diff --git a/.travis.yml b/.travis.yml
index b60270840..8879cfdf6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -35,7 +35,7 @@ env:
- CLOUDDATA_HOST=${CLOUDDATA_HOST:-$CLOUDDATA_HOST_STAGING}
- RECAPTCHA_SITE_KEY_master=6LeRbUwUAAAAAFYhKgk3G9OKWqE_OJ7Z-7VTUCbl
- RECAPTCHA_SITE_KEY_STAGING=6LfukK4UAAAAAFR44yoZMhv8fj6xh-PMiIxwryG3
- - RECAPTCHA_SITE=RECAPTCHA_SITE_KEY_$TRAVIS_BRANCH
+ - RECAPTCHA_SITE_KEY_VAR=RECAPTCHA_SITE_KEY_$TRAVIS_BRANCH
- RECAPTCHA_SITE_KEY=${!RECAPTCHA_SITE_KEY_VAR}
- RECAPTCHA_SITE_KEY=${RECAPTCHA_SITE_KEY:-$RECAPTCHA_SITE_KEY_STAGING}
- ROOT_URL_master=https://scratch.mit.edu
diff --git a/package-lock.json b/package-lock.json
index a70ed6992..57c8ea9d8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5,9 +5,9 @@
"requires": true,
"dependencies": {
"@babel/cli": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.6.0.tgz",
- "integrity": "sha512-1CTDyGUjQqW3Mz4gfKZ04KGOckyyaNmKneAMlABPS+ZyuxWv3FrVEVz7Ag08kNIztVx8VaJ8YgvYLSNlMKAT5Q==",
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.6.2.tgz",
+ "integrity": "sha512-JDZ+T/br9pPfT2lmAMJypJDTTTHM9ePD/ED10TRjRzJVdEVy+JB3iRlhzYmTt5YkNgHvxWGlUVnLtdv6ruiDrQ==",
"dev": true,
"requires": {
"chokidar": "^2.1.8",
@@ -479,17 +479,17 @@
}
},
"@babel/core": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.0.tgz",
- "integrity": "sha512-FuRhDRtsd6IptKpHXAa+4WPZYY2ZzgowkbLBecEDDSje1X/apG7jQM33or3NdOmjXBKWGOg4JmSiRfUfuTtHXw==",
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.2.tgz",
+ "integrity": "sha512-l8zto/fuoZIbncm+01p8zPSDZu/VuuJhAfA7d/AbzM09WR7iVhavvfNDYCNpo1VvLk6E6xgAoP9P+/EMJHuRkQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.5.5",
- "@babel/generator": "^7.6.0",
- "@babel/helpers": "^7.6.0",
- "@babel/parser": "^7.6.0",
+ "@babel/generator": "^7.6.2",
+ "@babel/helpers": "^7.6.2",
+ "@babel/parser": "^7.6.2",
"@babel/template": "^7.6.0",
- "@babel/traverse": "^7.6.0",
+ "@babel/traverse": "^7.6.2",
"@babel/types": "^7.6.0",
"convert-source-map": "^1.1.0",
"debug": "^4.1.0",
@@ -510,22 +510,21 @@
}
},
"@babel/generator": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.0.tgz",
- "integrity": "sha512-Ms8Mo7YBdMMn1BYuNtKuP/z0TgEIhbcyB8HVR6PPNYp4P61lMsABiS4A3VG1qznjXVCf3r+fVHhm4efTYVsySA==",
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.2.tgz",
+ "integrity": "sha512-j8iHaIW4gGPnViaIHI7e9t/Hl8qLjERI6DcV9kEpAIDJsAOrcnXqRS7t+QbhL76pwbtqP+QCQLL0z1CyVmtjjQ==",
"dev": true,
"requires": {
"@babel/types": "^7.6.0",
"jsesc": "^2.5.1",
"lodash": "^4.17.13",
- "source-map": "^0.5.0",
- "trim-right": "^1.0.1"
+ "source-map": "^0.5.0"
}
},
"@babel/parser": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.0.tgz",
- "integrity": "sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ==",
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.2.tgz",
+ "integrity": "sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==",
"dev": true
},
"@babel/template": {
@@ -540,16 +539,16 @@
}
},
"@babel/traverse": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.0.tgz",
- "integrity": "sha512-93t52SaOBgml/xY74lsmt7xOR4ufYvhb5c5qiM6lu4J/dWGMAfAh6eKw4PjLes6DI6nQgearoxnFJk60YchpvQ==",
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.2.tgz",
+ "integrity": "sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.5.5",
- "@babel/generator": "^7.6.0",
+ "@babel/generator": "^7.6.2",
"@babel/helper-function-name": "^7.1.0",
"@babel/helper-split-export-declaration": "^7.4.4",
- "@babel/parser": "^7.6.0",
+ "@babel/parser": "^7.6.2",
"@babel/types": "^7.6.0",
"debug": "^4.1.0",
"globals": "^11.1.0",
@@ -686,33 +685,32 @@
}
},
"@babel/helpers": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.6.0.tgz",
- "integrity": "sha512-W9kao7OBleOjfXtFGgArGRX6eCP0UEcA2ZWEWNkJdRZnHhW4eEbeswbG3EwaRsnQUAEGWYgMq1HsIXuNNNy2eQ==",
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.6.2.tgz",
+ "integrity": "sha512-3/bAUL8zZxYs1cdX2ilEE0WobqbCmKWr/889lf2SS0PpDcpEIY8pb1CCyz0pEcX3pEb+MCbks1jIokz2xLtGTA==",
"dev": true,
"requires": {
"@babel/template": "^7.6.0",
- "@babel/traverse": "^7.6.0",
+ "@babel/traverse": "^7.6.2",
"@babel/types": "^7.6.0"
},
"dependencies": {
"@babel/generator": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.0.tgz",
- "integrity": "sha512-Ms8Mo7YBdMMn1BYuNtKuP/z0TgEIhbcyB8HVR6PPNYp4P61lMsABiS4A3VG1qznjXVCf3r+fVHhm4efTYVsySA==",
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.2.tgz",
+ "integrity": "sha512-j8iHaIW4gGPnViaIHI7e9t/Hl8qLjERI6DcV9kEpAIDJsAOrcnXqRS7t+QbhL76pwbtqP+QCQLL0z1CyVmtjjQ==",
"dev": true,
"requires": {
"@babel/types": "^7.6.0",
"jsesc": "^2.5.1",
"lodash": "^4.17.13",
- "source-map": "^0.5.0",
- "trim-right": "^1.0.1"
+ "source-map": "^0.5.0"
}
},
"@babel/parser": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.0.tgz",
- "integrity": "sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ==",
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.2.tgz",
+ "integrity": "sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==",
"dev": true
},
"@babel/template": {
@@ -727,16 +725,16 @@
}
},
"@babel/traverse": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.0.tgz",
- "integrity": "sha512-93t52SaOBgml/xY74lsmt7xOR4ufYvhb5c5qiM6lu4J/dWGMAfAh6eKw4PjLes6DI6nQgearoxnFJk60YchpvQ==",
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.2.tgz",
+ "integrity": "sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.5.5",
- "@babel/generator": "^7.6.0",
+ "@babel/generator": "^7.6.2",
"@babel/helper-function-name": "^7.1.0",
"@babel/helper-split-export-declaration": "^7.4.4",
- "@babel/parser": "^7.6.0",
+ "@babel/parser": "^7.6.2",
"@babel/types": "^7.6.0",
"debug": "^4.1.0",
"globals": "^11.1.0",
@@ -1018,9 +1016,9 @@
}
},
"@types/babel__generator": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.0.2.tgz",
- "integrity": "sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ==",
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.0.tgz",
+ "integrity": "sha512-c1mZUu4up5cp9KROs/QAw0gTeHrw/x7m52LcnvMxxOZ03DmLwPV0MlGmlgzV3cnSdjhJOZsj7E7FHeioai+egw==",
"dev": true,
"requires": {
"@babel/types": "^7.0.0"
@@ -15561,15 +15559,15 @@
}
},
"scratch-gui": {
- "version": "0.1.0-prerelease.20190918163722",
- "resolved": "https://registry.npmjs.org/scratch-gui/-/scratch-gui-0.1.0-prerelease.20190918163722.tgz",
- "integrity": "sha512-2BM65eEvknpgrlkZgYP31H4v4rUsYuacY2NBBLwZ+zBScIsYMrfuxr2zPtvtxOkjYf9yH6TJef8/i/YZmPtfhw==",
+ "version": "0.1.0-prerelease.20190925145604",
+ "resolved": "https://registry.npmjs.org/scratch-gui/-/scratch-gui-0.1.0-prerelease.20190925145604.tgz",
+ "integrity": "sha512-NEL0AMDJaNNnl5mbN0YNL4rxxQW8nlsxAn9ZYUGScqoCLdHB30+0ipEWCnVLacIHCkbq1FcPXD237IOvnfT+8A==",
"dev": true
},
"scratch-l10n": {
- "version": "3.5.20190917223712",
- "resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.5.20190917223712.tgz",
- "integrity": "sha512-kWkeUD5An4RwbAVZU7OUqS25D1rrdI73ZYhNn3tQhGu545JaITZf37AvVQW4DyZU+wFHQ1G30nWls7bXZLEPbA==",
+ "version": "3.5.20190924223720",
+ "resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.5.20190924223720.tgz",
+ "integrity": "sha512-5bNisTEe9WJI+w3TQQ7XVutoFZuBKtHLLfijFd2vOkLv93XoxEQvpW62uz34uyj2tX9N8JPzCB+KaJalC+Z8Mg==",
"dev": true,
"requires": {
"@babel/cli": "^7.1.2",
diff --git a/package.json b/package.json
index cd265563a..80e423896 100644
--- a/package.json
+++ b/package.json
@@ -126,7 +126,7 @@
"redux-mock-store": "^1.2.3",
"redux-thunk": "2.0.1",
"sass-loader": "6.0.6",
- "scratch-gui": "0.1.0-prerelease.20190918163722",
+ "scratch-gui": "0.1.0-prerelease.20190925145604",
"scratch-l10n": "latest",
"selenium-webdriver": "3.6.0",
"slick-carousel": "1.6.0",
diff --git a/src/components/extension-landing/extension-landing.jsx b/src/components/extension-landing/extension-landing.jsx
index 44cb3fb01..c883d294f 100644
--- a/src/components/extension-landing/extension-landing.jsx
+++ b/src/components/extension-landing/extension-landing.jsx
@@ -1,7 +1,7 @@
const bindAll = require('lodash.bindall');
const React = require('react');
-const OS_ENUM = require('./os-enum.js');
+const detectOS = require('../../lib/detect-os.js').default;
class ExtensionLanding extends React.Component {
constructor (props) {
@@ -10,16 +10,8 @@ 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: detectedOS
+ OS: detectOS()
};
}
diff --git a/src/components/extension-landing/extension-requirements.jsx b/src/components/extension-landing/extension-requirements.jsx
index 67bc2ba46..3754eb02c 100644
--- a/src/components/extension-landing/extension-requirements.jsx
+++ b/src/components/extension-landing/extension-requirements.jsx
@@ -4,6 +4,8 @@ const React = require('react');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
+const {CHROME_APP_RELEASED} = require('../../lib/feature-flags.js');
+
require('./extension-landing.scss');
const ExtensionRequirements = props => (
@@ -12,13 +14,64 @@ const ExtensionRequirements = props => (
- {props.children}
+ {props.bluetoothStandard ? (
+
+
+
+ Windows 10 version 1709+
+
+
+
+ macOS 10.13+
+
+ {CHROME_APP_RELEASED && (
+
+
+
+ ChromeOS
+
+
+
+ Android 5.0+
+
+
+ )}
+
+
+ Bluetooth
+
+
+
+ Scratch Link
+
+
+ ) : props.children}
);
ExtensionRequirements.propTypes = {
+ bluetoothStandard: PropTypes.bool,
children: PropTypes.node
};
+ExtensionRequirements.defaultProps = {
+ bluetoothStandard: false
+};
+
module.exports = ExtensionRequirements;
diff --git a/src/components/extension-landing/install-scratch-link.jsx b/src/components/extension-landing/install-scratch-link.jsx
index b8186fbcf..507cdbeb7 100644
--- a/src/components/extension-landing/install-scratch-link.jsx
+++ b/src/components/extension-landing/install-scratch-link.jsx
@@ -2,13 +2,14 @@ const PropTypes = require('prop-types');
const FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
-const OS_ENUM = require('./os-enum.js');
+const OS_ENUM = require('../../lib/os-enum.js');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const Steps = require('../../components/steps/steps.jsx');
const Step = require('../../components/steps/step.jsx');
require('./extension-landing.scss');
+// Assumes this will only be called with an OS that needs Scratch Link
const InstallScratchLink = ({
currentOS
}) => (
@@ -37,20 +38,20 @@ const InstallScratchLink = ({
-
+
-
+
diff --git a/src/components/info-button/info-button.scss b/src/components/info-button/info-button.scss
index b776ee6a3..f2d93303d 100644
--- a/src/components/info-button/info-button.scss
+++ b/src/components/info-button/info-button.scss
@@ -7,6 +7,7 @@
width: 1rem;
height: 1rem;
margin-left: .375rem;
+ margin-top: -.25rem;
border-radius: 50%;
background-color: $type-gray-60percent;
background-image: url("/svgs/info-button/info-button.svg");
diff --git a/src/components/install-scratch/install-scratch.jsx b/src/components/install-scratch/install-scratch.jsx
new file mode 100644
index 000000000..eff760cff
--- /dev/null
+++ b/src/components/install-scratch/install-scratch.jsx
@@ -0,0 +1,146 @@
+const PropTypes = require('prop-types');
+const FormattedMessage = require('react-intl').FormattedMessage;
+const React = require('react');
+
+const OS_ENUM = require('../../lib/os-enum.js');
+const {CHROME_APP_RELEASED} = require('../../lib/feature-flags.js');
+
+const {isDownloaded, isFromGooglePlay} = require('./install-util.js');
+
+const FlexRow = require('../../components/flex-row/flex-row.jsx');
+const Steps = require('../../components/steps/steps.jsx');
+const Step = require('../../components/steps/step.jsx');
+
+require('./install-scratch.scss');
+
+const InstallScratch = ({
+ currentOS
+}) => (
+
+
+
+ {CHROME_APP_RELEASED ? (
+
+ ) : (
+
+ {isDownloaded(currentOS) && (
+
+ )}
+ {isFromGooglePlay(currentOS) && (
+
+ )}
+
+ )}
+
+
+
+
+
+
+ {isDownloaded(currentOS) && (
+
+ )}
+ {isFromGooglePlay(currentOS) && (
+
+ )}
+
+
+
+ {currentOS === OS_ENUM.WINDOWS && (
+
+
+
+ )}
+ {currentOS === OS_ENUM.MACOS && (
+
+
+
+ )}
+ {isFromGooglePlay(currentOS) && (
+
+
+
+
+ )}
+ {isDownloaded(currentOS) && (
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+ {isDownloaded(currentOS) && (
+
+
+ {currentOS === OS_ENUM.WINDOWS ?
+ :
+
+ }
+
+
+
+
+
+
+ )}
+
+
+
+);
+
+InstallScratch.propTypes = {
+ currentOS: PropTypes.string
+};
+
+module.exports = InstallScratch;
diff --git a/src/components/install-scratch/install-scratch.scss b/src/components/install-scratch/install-scratch.scss
new file mode 100644
index 000000000..953b93d5a
--- /dev/null
+++ b/src/components/install-scratch/install-scratch.scss
@@ -0,0 +1,95 @@
+@import "../../colors";
+@import "../../frameless";
+
+#view {
+ padding: 0;
+}
+
+.install-scratch {
+ padding: 2rem 0;
+
+ .inner {
+ align-items: flex-start;
+ }
+
+ .downloads-container {
+ text-align: center;
+
+ .horizontal-divider {
+ display: block;
+ margin: 20px;
+ }
+
+ .horizontal-divider:before,
+ .horizontal-divider:after {
+ display: inline-block;
+ position: relative;
+ background-color: $ui-dark-gray;
+ width: 50%;
+ height: 1px;
+ vertical-align: middle;
+ content: "";
+ }
+
+ .horizontal-divider:before {
+ right: .5em;
+ margin-left: -50%;
+ }
+
+ .horizontal-divider:after {
+ left: .5em;
+ margin-right: -50%;
+ }
+ }
+
+ .step-image {
+ height: 14rem;
+ }
+
+ .title {
+ margin-bottom: 2rem;
+ font-size: 2rem;
+ }
+
+ .legacy-link {
+ display: flex;
+ }
+
+ .download-button {
+ display: inline-block;
+ margin: .5em 0;
+ border: 0;
+ border-radius: 8px;
+ background-color: $ui-blue;
+ cursor: pointer;
+ padding: 1rem 2rem;
+ color: $ui-white;
+ font-size: 1rem;
+ }
+
+ .macos-badge img {
+ height: 50px;
+ }
+
+ .ms-badge img {
+ height: 50px;
+ }
+
+ .play-badge img {
+ height: 50px;
+ }
+
+ .download-image {
+ width: 100%;
+ max-width: $cols6;
+
+ img {
+ max-width: 100%;
+ max-height: 100%;
+ }
+ }
+
+ .blue {
+ background-color: $ui-blue-10percent;
+ }
+}
diff --git a/src/components/install-scratch/install-util.js b/src/components/install-scratch/install-util.js
new file mode 100644
index 000000000..20f667c21
--- /dev/null
+++ b/src/components/install-scratch/install-util.js
@@ -0,0 +1,13 @@
+const OS_ENUM = require('../../lib/os-enum.js');
+
+module.exports = {};
+
+module.exports.isDownloaded = os => {
+ if (os === OS_ENUM.WINDOWS || os === OS_ENUM.MACOS) return true;
+ return false;
+};
+
+module.exports.isFromGooglePlay = os => {
+ if (os === OS_ENUM.ANDROID || os === OS_ENUM.CHROMEOS) return true;
+ return false;
+};
diff --git a/src/components/join-flow/birthdate-step.jsx b/src/components/join-flow/birthdate-step.jsx
index 4ed3405b9..7e507468b 100644
--- a/src/components/join-flow/birthdate-step.jsx
+++ b/src/components/join-flow/birthdate-step.jsx
@@ -85,7 +85,8 @@ class BirthDateStep extends React.Component {
const {
errors,
handleSubmit,
- isSubmitting
+ isSubmitting,
+ setFieldError
} = props;
return (
setFieldError('birth_month', null)}
+ /* eslint-enable react/jsx-no-bind */
/>
setFieldError('birth_year', null)}
+ /* eslint-enable react/jsx-no-bind */
/>
diff --git a/src/components/join-flow/country-step.jsx b/src/components/join-flow/country-step.jsx
index 9d44379fe..9b697439a 100644
--- a/src/components/join-flow/country-step.jsx
+++ b/src/components/join-flow/country-step.jsx
@@ -30,7 +30,7 @@ class CountryStep extends React.Component {
this.countryOptions = [...countryData.registrationCountryOptions];
this.countryOptions.unshift({ // add placeholder as first option
disabled: true,
- label: this.props.intl.formatMessage({id: 'general.country'}),
+ label: this.props.intl.formatMessage({id: 'registration.selectCountry'}),
value: 'null'
});
}
@@ -64,7 +64,8 @@ class CountryStep extends React.Component {
const {
errors,
handleSubmit,
- isSubmitting
+ isSubmitting,
+ setFieldError
} = props;
return (
setFieldError('country', null)}
+ /* eslint-enable react/jsx-no-bind */
/>
{/* note that this is a hidden checkbox the user will never see */}
diff --git a/src/components/join-flow/join-flow-step.scss b/src/components/join-flow/join-flow-step.scss
index 5c27e4253..e7de82007 100644
--- a/src/components/join-flow/join-flow-step.scss
+++ b/src/components/join-flow/join-flow-step.scss
@@ -27,7 +27,7 @@
.join-flow-description {
font-size: .875rem;
font-weight: bold;
- line-height: 1.37500rem;
+ line-height: 1.125rem;
margin-top: 0.78125rem;
margin-bottom: 1.875rem;
text-align: center;
diff --git a/src/components/join-flow/join-flow-steps.scss b/src/components/join-flow/join-flow-steps.scss
index abddb3b99..de027bbe3 100644
--- a/src/components/join-flow/join-flow-steps.scss
+++ b/src/components/join-flow/join-flow-steps.scss
@@ -39,28 +39,40 @@
}
.validation-full-width-input {
- transform: translate(21.5625rem, 0);
+ transform: translate(21.8125rem, 0);
}
.validation-birthdate-month {
- transform: translate(-9.25rem, 0);
- width: 7.25rem;
+ transform: translate(-9.875rem, 0);
}
.validation-birthdate-year {
- transform: translate(8.75rem, 0);
- width: 7.25rem;
+ transform: translate(9.375rem, 0);
}
@media #{$intermediate-and-smaller} {
.validation-full-width-input {
transform: unset;
margin-bottom: .75rem;
+ max-width: 100%;
}
- .validation-birthdate-input {
+ .validation-country {
+ top: .5rem;
+ }
+
+ .validation-birthdate {
transform: unset;
- width: 8rem;
+ top: .5rem;
+ width: 19rem;
+ }
+
+ .validation-birthdate-month {
+ margin-right: -9.25rem;
+ }
+
+ .validation-birthdate-year {
+ margin-left: -9.625rem;
}
}
@@ -114,8 +126,6 @@
}
.join-flow-inner-gender-step {
- /* need height so that flex will adjust children proportionately */
- height: 27.25rem;
padding-top: 2.625rem;
padding-bottom: 1rem;
}
@@ -177,7 +187,7 @@
.gender-radio-row {
transition: all .125s ease;
width: 20.875rem;
- height: 2.85rem;
+ min-height: 2.85rem;
background-color: $ui-gray;
border-radius: .5rem;
margin: 0 auto 0.375rem;
diff --git a/src/components/join-flow/join-flow.jsx b/src/components/join-flow/join-flow.jsx
index 6b18d6a40..2d40452e2 100644
--- a/src/components/join-flow/join-flow.jsx
+++ b/src/components/join-flow/join-flow.jsx
@@ -23,6 +23,7 @@ class JoinFlow extends React.Component {
super(props);
bindAll(this, [
'handleAdvanceStep',
+ 'handleRegistrationError',
'handlePrepareToRegister',
'handleRegistrationResponse',
'handleSubmitRegistration'
@@ -34,6 +35,14 @@ class JoinFlow extends React.Component {
waiting: false
};
}
+ handleRegistrationError (message) {
+ if (!message) {
+ message = this.props.intl.formatMessage({
+ id: 'registration.generalError'
+ });
+ }
+ this.setState({registrationError: message});
+ }
handlePrepareToRegister (newFormData) {
newFormData = newFormData || {};
const newState = {
@@ -143,8 +152,10 @@ class JoinFlow extends React.Component {
- }
+ ) : (
+
+ )}
title={`${this.props.intl.formatMessage(
{id: 'registration.welcomeStepTitleNonEducator'},
{username: this.props.username}
@@ -79,6 +81,7 @@ class WelcomeStep extends React.Component {
}
WelcomeStep.propTypes = {
+ createProjectOnComplete: PropTypes.bool,
email: PropTypes.string,
intl: intlShape,
onNextStep: PropTypes.func,
diff --git a/src/components/modal/base/modal.jsx b/src/components/modal/base/modal.jsx
index 156d435ce..0203a0f41 100644
--- a/src/components/modal/base/modal.jsx
+++ b/src/components/modal/base/modal.jsx
@@ -49,16 +49,20 @@ class Modal extends React.Component {
}}
{...omit(this.props, ['className', 'overlayClassName'])}
>
-
-
-
+
+ {this.props.showCloseButton && (
+
+
+
+ )}
{this.props.children}
);
@@ -69,7 +73,11 @@ Modal.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
overlayClassName: PropTypes.string,
+ showCloseButton: PropTypes.bool,
useStandardSizes: PropTypes.bool
};
+Modal.defaultProps = {
+ showCloseButton: true
+};
module.exports = Modal;
diff --git a/src/components/modal/join/modal.jsx b/src/components/modal/join/modal.jsx
index d309433ee..7edc22a3c 100644
--- a/src/components/modal/join/modal.jsx
+++ b/src/components/modal/join/modal.jsx
@@ -6,26 +6,32 @@ const JoinFlow = require('../../join-flow/join-flow.jsx');
require('./modal.scss');
const JoinModal = ({
- onCompleteRegistration, // eslint-disable-line no-unused-vars
+ createProjectOnComplete,
+ onCompleteRegistration,
onRequestClose,
...modalProps
}) => (
);
JoinModal.propTypes = {
+ createProjectOnComplete: PropTypes.bool,
onCompleteRegistration: PropTypes.func,
- onRequestClose: PropTypes.func
+ onRequestClose: PropTypes.func,
+ showCloseButton: PropTypes.bool
};
module.exports = JoinModal;
diff --git a/src/components/os-chooser/os-chooser.jsx b/src/components/os-chooser/os-chooser.jsx
index 977c207ea..5d77d19e2 100644
--- a/src/components/os-chooser/os-chooser.jsx
+++ b/src/components/os-chooser/os-chooser.jsx
@@ -4,11 +4,12 @@ const FormattedMessage = require('react-intl').FormattedMessage;
const PropTypes = require('prop-types');
const React = require('react');
+const {CHROME_APP_RELEASED} = require('../../lib/feature-flags.js');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const Button = require('../../components/forms/button.jsx');
-const OS_ENUM = require('../../components/extension-landing/os-enum.js');
+const OS_ENUM = require('../../lib/os-enum.js');
require('./os-chooser.scss');
@@ -34,6 +35,28 @@ const OSChooser = props => (
macOS
+ {CHROME_APP_RELEASED && (
+
+
+
+
+ )}
);
diff --git a/src/components/registration/scratch3-registration.jsx b/src/components/registration/scratch3-registration.jsx
index 220c01433..f88031b8e 100644
--- a/src/components/registration/scratch3-registration.jsx
+++ b/src/components/registration/scratch3-registration.jsx
@@ -8,14 +8,18 @@ const JoinModal = require('../modal/join/modal.jsx');
require('./registration.scss');
const Registration = ({
+ createProjectOnComplete,
handleCloseRegistration,
handleCompleteRegistration,
- isOpen
+ isOpen,
+ showCloseButton
}) => (
@@ -23,11 +27,11 @@ const Registration = ({
);
Registration.propTypes = {
- // used in mapDispatchToProps; eslint doesn't understand that this prop is used
- createProjectOnComplete: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
+ createProjectOnComplete: PropTypes.bool,
handleCloseRegistration: PropTypes.func,
handleCompleteRegistration: PropTypes.func,
- isOpen: PropTypes.bool
+ isOpen: PropTypes.bool,
+ showCloseButton: PropTypes.bool
};
const mapDispatchToProps = (dispatch, ownProps) => ({
diff --git a/src/l10n.json b/src/l10n.json
index 9b9032b7a..b8f94ce88 100644
--- a/src/l10n.json
+++ b/src/l10n.json
@@ -13,6 +13,7 @@
"general.confirmEmail": "Confirm Email",
"general.contactUs": "Contact Us",
"general.contact": "Contact",
+ "general.done": "Done",
"general.downloadPDF": "Download PDF",
"general.emailUs": "Email Us",
"general.conferences": "Conferences",
@@ -133,10 +134,17 @@
"oschooser.choose": "Choose your OS:",
+ "installScratch.or": "or",
+ "installScratch.directDownload": "Direct download",
+ "installScratch.desktopHeaderTitle": "Install Scratch Desktop",
+ "installScratch.appHeaderTitle": "Install Scratch for {operatingsystem}",
+ "installScratch.downloadScratchDesktop": "Download Scratch Desktop",
+ "installScratch.downloadScratchAppGeneric": "Download Scratch for {operatingsystem}",
+ "installScratch.getScratchAppPlay": "Get Scratch on the Google Play Store",
+ "installScratch.useScratchApp": "Open the Scratch app on your device.",
+
"installScratchLink.installHeaderTitle": "Install Scratch Link",
"installScratchLink.downloadAndInstall": "Download and install Scratch Link.",
- "installScratchLink.or": "or",
- "installScratchLink.directDownload": "Direct download",
"installScratchLink.startScratchLink": "Start Scratch Link and make sure it is running. It should appear in your toolbar.",
"parents.FaqAgeRangeA": "While Scratch is primarily designed for 8 to 16 year olds, it is also used by people of all ages, including younger children with their parents.",
@@ -184,9 +192,10 @@
"registration.personalStepDescription": "Your individual responses will not be displayed publicly, and will be kept confidential and secure",
"registration.private": "We will keep this information private.",
"registration.receiveEmails": "I'd like to receive emails from the Scratch Team about project ideas, events, and more.",
- "registration.selectCountry": "select country",
+ "registration.selectCountry": "Select country",
"registration.studentPersonalStepDescription": "This information will not appear on the Scratch website.",
"registration.showPassword": "Show password",
+ "registration.troubleReload": "Scratch is having trouble finishing registration. Try reloading the page or try again in another browser.",
"registration.usernameStepDescription": "Fill in the following forms to request an account. The approval process may take up to one day.",
"registration.usernameStepDescriptionNonEducator": "Create projects, share ideas, make friends. It’s free!",
"registration.usernameStepRealName": "Please do not use any portion of your real name in your username.",
diff --git a/src/lib/detect-os.js b/src/lib/detect-os.js
new file mode 100644
index 000000000..fa94c3229
--- /dev/null
+++ b/src/lib/detect-os.js
@@ -0,0 +1,18 @@
+import bowser from 'bowser';
+import OS_ENUM from './os-enum.js';
+import {CHROME_APP_RELEASED} from './feature-flags.js';
+
+/**
+ * Helper function to the current Operating System.
+ * @returns {OS_ENUM} Returns the OS value, defaults to WINDOWS
+ */
+export default function () {
+ // matching OS strings from https://github.com/lancedikson/bowser/blob/master/src/constants.js
+ if (bowser.osname === 'macOS') return OS_ENUM.MACOS;
+ if (CHROME_APP_RELEASED) {
+ if (bowser.osname === 'Chrome OS') return OS_ENUM.CHROMEOS;
+ if (bowser.osname === 'Android') return OS_ENUM.ANDROID;
+ }
+ // if (bowser.osname === 'iOS') return OS_ENUM.IOS; // @todo
+ return OS_ENUM.WINDOWS;
+}
diff --git a/src/lib/feature-flags.js b/src/lib/feature-flags.js
new file mode 100644
index 000000000..2965c1e9c
--- /dev/null
+++ b/src/lib/feature-flags.js
@@ -0,0 +1,10 @@
+const isStaging = () => process.env.SCRATCH_ENV === 'staging';
+
+const flagInUrl = flag => {
+ const url = (window.location && window.location.search) || '';
+ return url.indexOf(`${flag}=true`) !== -1;
+};
+
+module.exports = {
+ CHROME_APP_RELEASED: isStaging() && flagInUrl('CHROME_APP_RELEASED')
+};
diff --git a/src/components/extension-landing/os-enum.js b/src/lib/os-enum.js
similarity index 51%
rename from src/components/extension-landing/os-enum.js
rename to src/lib/os-enum.js
index b8cde494e..24d41c018 100644
--- a/src/components/extension-landing/os-enum.js
+++ b/src/lib/os-enum.js
@@ -1,6 +1,8 @@
const OS_ENUM = {
WINDOWS: 'Windows',
- MACOS: 'macOS'
+ MACOS: 'macOS',
+ CHROMEOS: 'ChromeOS',
+ ANDROID: 'Android'
};
module.exports = OS_ENUM;
diff --git a/src/views/boost/boost.jsx b/src/views/boost/boost.jsx
index 1e3203ea6..5c3b885eb 100644
--- a/src/views/boost/boost.jsx
+++ b/src/views/boost/boost.jsx
@@ -9,12 +9,14 @@ const render = require('../../lib/render.jsx');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const OSChooser = require('../../components/os-chooser/os-chooser.jsx');
+const {isDownloaded, isFromGooglePlay} = require('../../components/install-scratch/install-util.js');
const ExtensionLanding = require('../../components/extension-landing/extension-landing.jsx');
const ExtensionHeader = require('../../components/extension-landing/extension-header.jsx');
const ExtensionRequirements = require('../../components/extension-landing/extension-requirements.jsx');
const ExtensionSection = require('../../components/extension-landing/extension-section.jsx');
const InstallScratchLink = require('../../components/extension-landing/install-scratch-link.jsx');
+const InstallScratch = require('../../components/install-scratch/install-scratch.jsx');
const ProjectCard = require('../../components/extension-landing/project-card.jsx');
const Steps = require('../../components/steps/steps.jsx');
@@ -56,45 +58,23 @@ class Boost extends ExtensionLanding {
src="/images/boost/boost-header.svg"
/>}
renderRequirements={
-
-
-
- Windows 10 version 1709+
-
-
-
- macOS 10.13+
-
-
-
- Bluetooth 4.0
-
-
-
- Scratch Link
-
-
+
}
/>
-
+ {(isDownloaded(this.state.OS)) && (
+
+ )}
+ {(isFromGooglePlay(this.state.OS)) && (
+
+ )}
@@ -109,20 +89,25 @@ class Boost extends ExtensionLanding {
/>
-
- Scratch
-
- )
- }}
- />
+ {isDownloaded(this.state.OS) && (
+
+ Scratch
+
+ )
+ }}
+ />
+ )}
+ {isFromGooglePlay(this.state.OS) && (
+
+ )}
@@ -208,36 +193,40 @@ class Boost extends ExtensionLanding {
-
-
-
-
-
-
-
-
-
- ),
- macOSVersionLink: (
-
-
-
- )
- }}
- />
-
+ {isDownloaded(this.state.OS) && (
+
+
+
+
+
+
+
+
+
+
+ ),
+ macOSVersionLink: (
+
+
+
+ )
+ }}
+ />
+
+
+ )}
diff --git a/src/views/boost/l10n.json b/src/views/boost/l10n.json
index 470781f62..b99a7a056 100644
--- a/src/views/boost/l10n.json
+++ b/src/views/boost/l10n.json
@@ -12,8 +12,8 @@
"boost.connectALegoBeam": "Connect a LEGO beam with an axle to motor A and click the block again to make it spin.",
"boost.starterProjects": "Starter Projects",
"boost.troubleshootingTitle": "Troubleshooting",
- "boost.avoidFirmwareUpdateTitle": "Having trouble connecting or using motor blocks?",
- "boost.avoidFirmwareUpdateText": "We suggest you avoid updating the firmware on your BOOST for now. We are working on a fix with the team at LEGO for users who have already updated their firmware. Stay tuned! We hope to have a fix released soon.",
+ "boost.updateScratchLinkTitle": "Make sure you have the latest version of Scratch Link",
+ "boost.updateScratchLinkText": "Install Scratch Link using the button above. We recommend using the app store installation process to help keep your version up to date.",
"boost.checkOSVersionTitle": "Make sure your operating system is compatible with Scratch Link",
"boost.checkOSVersionText": "The minimum operating system versions are listed at the top of this page. See instructions for checking your version of {winOSVersionLink} or {macOSVersionLink}.",
"boost.winOSVersionLinkText": "Windows",
diff --git a/src/views/download/download.jsx b/src/views/download/download.jsx
index 2c935f04b..b0f77a4a8 100644
--- a/src/views/download/download.jsx
+++ b/src/views/download/download.jsx
@@ -5,13 +5,14 @@ const React = require('react');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const bindAll = require('lodash.bindall');
-const Steps = require('../../components/steps/steps.jsx');
-const Step = require('../../components/steps/step.jsx');
const Page = require('../../components/page/www/page.jsx');
const render = require('../../lib/render.jsx');
-const OS_ENUM = require('../../components/extension-landing/os-enum.js');
+const detectOS = require('../../lib/detect-os.js').default;
+const {CHROME_APP_RELEASED} = require('../../lib/feature-flags.js');
const OSChooser = require('../../components/os-chooser/os-chooser.jsx');
+const InstallScratch = require('../../components/install-scratch/install-scratch.jsx');
+const {isDownloaded, isFromGooglePlay} = require('../../components/install-scratch/install-util.js');
require('./download.scss');
require('../../components/forms/button.scss');
@@ -22,15 +23,9 @@ class Download extends React.Component {
bindAll(this, [
'onSetOS'
]);
- let detectedOS = OS_ENUM.WINDOWS;
- if (window.navigator && window.navigator.platform) {
- if (window.navigator.platform === 'MacIntel') {
- detectedOS = OS_ENUM.MACOS;
- }
- }
this.state = {
- OS: detectedOS
+ OS: detectOS()
};
}
@@ -55,10 +50,16 @@ class Download extends React.Component {
src="/images/download/icon.png"
width="40"
/>
-
+
-
+
@@ -80,6 +81,24 @@ class Download extends React.Component {
/>
macOS 10.13+
+ {CHROME_APP_RELEASED && (
+
+
+
+ ChromeOS
+
+
+
+ Android 5.0+
+
+
+ )}
@@ -95,89 +114,150 @@ class Download extends React.Component {
currentOS={this.state.OS}
handleSetOS={this.onSetOS}
/>
-
-
-
-
-
-
-
-
-
-
-
-
+
+ {isDownloaded(this.state.OS) && (
+
-
-
- {this.state.OS === OS_ENUM.WINDOWS ?
- :
-
- }
-
-
-
-
+
-
-
-
-
+
+
+
+
+ )}
+ {isDownloaded(this.state.OS) && (
+
+
+
+
+
+
+
+
+ )}
+ {isFromGooglePlay(this.state.OS) && (
+
+
+
+
+
+
+
+
+ )}
-
-
-
-
-
-
-
+ {isFromGooglePlay(this.state.OS) ?
+ :
+
+ }
-
-
-
-
-
-
-
-
-
-
-
-
+ {isDownloaded(this.state.OS) && (CHROME_APP_RELEASED ? (
+
+
+
+
+
+
+
+
+ ) : (
+
+
+
+
+
+
+
+
+ ))}
+ {isFromGooglePlay(this.state.OS) && (
+
+
+
+
+
+
+
+
+ )}
+ {!CHROME_APP_RELEASED && (
+
+
+
+
+
+
+
+
+ )}
@@ -186,64 +266,7 @@ class Download extends React.Component {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
);
diff --git a/src/views/download/download.scss b/src/views/download/download.scss
index e53275950..3e2b4064c 100644
--- a/src/views/download/download.scss
+++ b/src/views/download/download.scss
@@ -27,6 +27,18 @@
font-size: 1rem;
}
+ .macos-badge img {
+ height: 50px;
+ }
+
+ .ms-badge img {
+ height: 50px;
+ }
+
+ .play-badge img {
+ height: 50px;
+ }
+
.download-header {
background-color: $ui-blue;
padding: 4rem 0;
@@ -80,6 +92,7 @@
.download-requirements {
justify-content: space-between;
+ line-height: 2rem;
}
.download-requirements span {
diff --git a/src/views/download/l10n.json b/src/views/download/l10n.json
index e475c8a75..c1559b070 100644
--- a/src/views/download/l10n.json
+++ b/src/views/download/l10n.json
@@ -1,11 +1,10 @@
{
"download.title": "Scratch Desktop",
"download.intro": "You can install the Scratch Desktop editor to work on projects without an internet connection. This version will work on Windows and MacOS.",
+ "download.appTitle": "Download Scratch",
+ "download.appIntro": "You can install Scratch for free to work on projects without an internet connection.",
"download.requirements": "Requirements",
"download.imgAltDownloadIllustration" : "Scratch 3.0 Desktop screenshot",
- "download.installHeaderTitle": "Install Scratch Desktop",
- "download.downloadScratchDesktop": "Download Scratch Desktop",
- "download.downloadButton": "Download",
"download.troubleshootingTitle": "FAQ",
"download.startScratchDesktop": "Start Scratch Desktop",
"download.howDoIInstall": "How do I install Scratch Desktop?",
@@ -14,17 +13,23 @@
"download.supportChromeOS" : "When will you have Scratch Desktop for Chromebooks?",
"download.supportChromeOSAnswer": "Scratch Desktop for Chromebooks is not yet available. We are working on it and expect to release later in 2019.",
"download.olderVersionsTitle" : "Older Versions",
- "download.olderVersions": "Looking for the Scratch 2.0 Offline Editor or Scratch 1.4?",
- "download.scratch1-4Desktop" : "Scratch 1.4 Desktop",
- "download.scratch2Desktop" : "Scratch 2.0 Desktop",
+ "download.olderVersions": "Looking for earlier Scratch Offline Editors?",
+ "download.scratch1-4Desktop" : "Scratch 1.4",
+ "download.scratch2Desktop" : "Scratch 2.0 Offline Editor",
"download.cannotAccessMacStore" : "What if I can't access the Mac App Store?",
"download.cannotAccessWindowsStore" : "What if I can't access the Microsoft Store?",
"download.macMoveToApplications" : "Open the .dmg file. Move Scratch Desktop into Applications.",
"download.winMoveToApplications" : "Run the .exe file.",
"download.canIUseScratchLink" : "Can I use Scratch Link to connect to extensions?",
"download.canIUseScratchLinkAnswer" : "Yes. However, you will need an Internet connection to use Scratch Link.",
+ "download.canIUseExtensions" : "Can I connect to hardware extensions?",
+ "download.canIUseExtensionsAnswer" : "Yes. With the Scratch app you can connect to extensions, and you do not need Scratch Link.",
"download.desktopAndBrowser": "Can I use Scratch Desktop and also have Scratch open in the browser?",
+ "download.appAndBrowser": "Can I use the Scratch app and also have Scratch open in the browser?",
"download.yesAnswer" : "Yes.",
"download.canIShare": "Can I share from Scratch Desktop?",
- "download.canIShareAnswer": "This isn’t supported currently. For now, you can save a project from Scratch Desktop, upload it to your Scratch account, and share it there. In a later version we will add the ability to upload to your Scratch account directly in Scratch Desktop."
+ "download.canIShareAnswer": "This isn’t supported currently. For now, you can save a project from Scratch Desktop, upload it to your Scratch account, and share it there. In a later version we will add the ability to upload to your Scratch account directly in Scratch Desktop.",
+ "download.canIShareApp": "Can I share from Scratch for {operatingsystem}?",
+ "download.canIShareAnswerPlayStore": "Yes. Click the 3-dots menu on a project in the lobby and select Share from the options. In addition to sharing by email you can sign in to your Scratch account and share the project on the Scratch Community.",
+ "download.canIShareAnswerDownloaded": "This isn’t supported currently. For now, you can save a project from Scratch for {operatingsystem}, upload it to your Scratch account, and share it there. In a later version we will add the ability to upload to your Scratch account directly in Scratch for {operatingsystem}."
}
diff --git a/src/views/ev3/ev3.jsx b/src/views/ev3/ev3.jsx
index a03b55a14..c6ca1c240 100644
--- a/src/views/ev3/ev3.jsx
+++ b/src/views/ev3/ev3.jsx
@@ -17,13 +17,15 @@ const ExtensionVideo = require('../../components/extension-landing/extension-vid
const ExtensionRequirements = require('../../components/extension-landing/extension-requirements.jsx');
const ExtensionSection = require('../../components/extension-landing/extension-section.jsx');
const InstallScratchLink = require('../../components/extension-landing/install-scratch-link.jsx');
+const InstallScratch = require('../../components/install-scratch/install-scratch.jsx');
const TipBox = require('../../components/extension-landing/tip-box.jsx');
const ProjectCard = require('../../components/extension-landing/project-card.jsx');
const Steps = require('../../components/steps/steps.jsx');
const Step = require('../../components/steps/step.jsx');
-const OS_ENUM = require('../../components/extension-landing/os-enum.js');
+const OS_ENUM = require('../../lib/os-enum.js');
+const {isDownloaded, isFromGooglePlay} = require('../../components/install-scratch/install-util.js');
require('../../components/extension-landing/extension-landing.scss');
require('./ev3.scss');
@@ -60,42 +62,23 @@ class EV3 extends ExtensionLanding {
videoId="0huu6wfiki"
/>}
renderRequirements={
-
-
-
- Windows 10 version 1709+
-
-
-
- macOS 10.13+
-
-
-
- Bluetooth
-
-
-
- Scratch Link
-
-
+
}
/>
-
+ {(isDownloaded(this.state.OS)) && (
+
+ )}
+ {(isFromGooglePlay(this.state.OS)) && (
+
+ )}
@@ -119,20 +102,25 @@ class EV3 extends ExtensionLanding {
/>
-
- Scratch
-
- )
- }}
- />
+ {(isDownloaded(this.state.OS)) && (
+
+ Scratch
+
+ )
+ }}
+ />
+ )}
+ {(isFromGooglePlay(this.state.OS)) && (
+
+ )}
@@ -168,27 +156,51 @@ class EV3 extends ExtensionLanding {
-
-
-
-
- {this.state.OS === OS_ENUM.WINDOWS ?
- :
-
- }
-
+ {this.state.OS === OS_ENUM.WINDOWS && (
+
+
+
+
+
+
+
+
+ )}
+ {this.state.OS === OS_ENUM.MACOS && (
+
+
+
+
+
+
+
+
+ )}
+ {this.state.OS === OS_ENUM.CHROMEOS && (
+
+
+
+
+
+
+
+
+ )}
@@ -269,32 +281,36 @@ class EV3 extends ExtensionLanding {
-
-
-
-
-
- ),
- macOSVersionLink: (
-
-
-
- )
- }}
- />
-
+ {isDownloaded(this.state.OS) && (
+
+
+
+
+
+
+ ),
+ macOSVersionLink: (
+
+
+
+ )
+ }}
+ />
+
+
+ )}
}
renderRequirements={
-
-
-
- Windows 10 version 1709+
-
-
-
- macOS 10.13+
-
-
-
- Bluetooth 4.0
-
-
-
- Scratch Link
-
-
+
}
/>
-
+ {(isDownloaded(this.state.OS)) && (
+
+ )}
+ {(isFromGooglePlay(this.state.OS)) && (
+
+ )}
@@ -117,20 +97,25 @@ class GdxFor extends ExtensionLanding {
/>
-
- Scratch
-
- )
- }}
- />
+ {isDownloaded(this.state.OS) && (
+
+ Scratch
+
+ )
+ }}
+ />
+ )}
+ {isFromGooglePlay(this.state.OS) && (
+
+ )}
@@ -221,32 +206,36 @@ class GdxFor extends ExtensionLanding {
-
-
-
-
-
- ),
- macOSVersionLink: (
-
-
-
- )
- }}
- />
-
+ {isDownloaded(this.state.OS) && (
+
+
+
+
+
+
+ ),
+ macOSVersionLink: (
+
+
+
+ )
+ }}
+ />
+
+
+ )}
diff --git a/src/views/join/join.jsx b/src/views/join/join.jsx
index ebb4019fc..4b2b9e6b5 100644
--- a/src/views/join/join.jsx
+++ b/src/views/join/join.jsx
@@ -1,16 +1,30 @@
const React = require('react');
const render = require('../../lib/render.jsx');
-const JoinModal = require('../../components/modal/join/modal.jsx');
+const Scratch3Registration = require('../../components/registration/scratch3-registration.jsx');
const ErrorBoundary = require('../../components/errorboundary/errorboundary.jsx');
// Require this even though we don't use it because, without it, webpack runs out of memory...
const Page = require('../../components/page/www/page.jsx'); // eslint-disable-line no-unused-vars
-const openModal = true;
+require('./join.scss');
const Register = () => (
-
+
+
+
+
+
+
);
diff --git a/src/views/join/join.scss b/src/views/join/join.scss
new file mode 100644
index 000000000..9aefc03fa
--- /dev/null
+++ b/src/views/join/join.scss
@@ -0,0 +1,29 @@
+@import "../../frameless";
+
+.join {
+ position: absolute;
+ z-index: 1000;
+ top: 12px;
+ left: 12px;
+ left: calc(25% - 76px);
+
+ .logo {
+ width: 76px;
+ }
+}
+
+@media #{$small} {
+ .join {
+ left: calc(50% - 38px);
+ }
+}
+@media #{$medium} {
+ .join {
+ left: calc(50% - 38px);
+ }
+}
+@media #{$intermediate} {
+ .join {
+ left: calc(50% - 38px);
+ }
+}
diff --git a/src/views/microbit/l10n.json b/src/views/microbit/l10n.json
index fd97d6ef6..4b57e29c2 100644
--- a/src/views/microbit/l10n.json
+++ b/src/views/microbit/l10n.json
@@ -7,6 +7,7 @@
"microbit.downloadCardsTitle": "Download micro:bit Cards",
"microbit.downloadHex": "Download the Scratch micro:bit HEX file",
"microbit.dragDropHex": "Drag and drop the HEX file onto your micro:bit",
+ "microbit.installHexAndroid": "Please follow the instructions to install the HEX file on a computer running Windows, MacOS or ChromeOS.",
"microbit.connectingMicrobit": "Connecting micro:bit to Scratch",
"microbit.powerMicrobit": "Power your micro:bit with USB or a battery pack.",
"microbit.useScratch3": "Use the {scratch3Link} editor.",
diff --git a/src/views/microbit/microbit.jsx b/src/views/microbit/microbit.jsx
index 1b36c4ba3..9e9beda27 100644
--- a/src/views/microbit/microbit.jsx
+++ b/src/views/microbit/microbit.jsx
@@ -10,19 +10,21 @@ const render = require('../../lib/render.jsx');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const OSChooser = require('../../components/os-chooser/os-chooser.jsx');
+const {isDownloaded, isFromGooglePlay} = require('../../components/install-scratch/install-util.js');
const ExtensionLanding = require('../../components/extension-landing/extension-landing.jsx');
const ExtensionHeader = require('../../components/extension-landing/extension-header.jsx');
const ExtensionRequirements = require('../../components/extension-landing/extension-requirements.jsx');
const ExtensionSection = require('../../components/extension-landing/extension-section.jsx');
const InstallScratchLink = require('../../components/extension-landing/install-scratch-link.jsx');
+const InstallScratch = require('../../components/install-scratch/install-scratch.jsx');
const ProjectCard = require('../../components/extension-landing/project-card.jsx');
const Button = require('../../components/forms/button.jsx');
const Steps = require('../../components/steps/steps.jsx');
const Step = require('../../components/steps/step.jsx');
-const OS_ENUM = require('../../components/extension-landing/os-enum.js');
+const OS_ENUM = require('../../lib/os-enum.js');
require('../../components/extension-landing/extension-landing.scss');
require('./microbit.scss');
@@ -59,90 +61,96 @@ class MicroBit extends ExtensionLanding {
src="/images/microbit/microbit-heart.png"
/>}
renderRequirements={
-
-
-
- Windows 10 version 1709+
-
-
-
- macOS 10.13+
-
-
-
- Bluetooth 4.0
-
-
-
- Scratch Link
-
-
+
}
/>
-
+ {(isDownloaded(this.state.OS)) && (
+
+ )}
+ {(isFromGooglePlay(this.state.OS)) && (
+
+ )}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {this.state.OS !== OS_ENUM.ANDROID && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {this.state.OS === OS_ENUM.WINDOWS && (
+
+
+
+ )}
+ {this.state.OS === OS_ENUM.MACOS && (
+
+
+
+ )}
+ {this.state.OS === OS_ENUM.CHROMEOS && (
+
+
+
+ )}
+
+
+
+
+
+ )}
+ {this.state.OS === OS_ENUM.ANDROID && (
+
+
+
+
+
+
+
+ )}
+
@@ -166,20 +174,25 @@ class MicroBit extends ExtensionLanding {
/>
-
- Scratch
-
- )
- }}
- />
+ {isDownloaded(this.state.OS) && (
+
+ Scratch
+
+ )
+ }}
+ />
+ )}
+ {isFromGooglePlay(this.state.OS) && (
+
+ )}
@@ -307,32 +320,36 @@ class MicroBit extends ExtensionLanding {
-
-
-
-
-
- ),
- macOSVersionLink: (
-
-
-
- )
- }}
- />
-
+ {isDownloaded(this.state.OS) && (
+
+
+
+
+
+
+ ),
+ macOSVersionLink: (
+
+
+
+ )
+ }}
+ />
+
+
+ )}
diff --git a/src/views/preview/meta.jsx b/src/views/preview/meta.jsx
index 57d0758a3..d526515a1 100644
--- a/src/views/preview/meta.jsx
+++ b/src/views/preview/meta.jsx
@@ -4,7 +4,7 @@ const Helmet = require('react-helmet').default;
const projectShape = require('./projectshape.jsx').projectShape;
const Meta = props => {
- const {title, instructions, author} = props.projectInfo;
+ const {id, title, instructions, author} = props.projectInfo;
// Do not want to render any meta tags unless all the info is loaded
// Check only author (object) because it is ok to have empty string instructions
@@ -29,6 +29,10 @@ const Meta = props => {
content={truncatedInstructions}
property="og:description"
/>
+
);
};
diff --git a/src/views/teachers/landing/landing.jsx b/src/views/teachers/landing/landing.jsx
index 3b59db7a4..d4b0ac937 100644
--- a/src/views/teachers/landing/landing.jsx
+++ b/src/views/teachers/landing/landing.jsx
@@ -276,7 +276,7 @@ const Landing = props => (
id="teacherlanding.codeClub"
values={{
codeClubLink: (
-
+
)
diff --git a/src/views/wedo2/wedo2.jsx b/src/views/wedo2/wedo2.jsx
index 21506aa64..3a4714cfe 100644
--- a/src/views/wedo2/wedo2.jsx
+++ b/src/views/wedo2/wedo2.jsx
@@ -9,6 +9,7 @@ const render = require('../../lib/render.jsx');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const OSChooser = require('../../components/os-chooser/os-chooser.jsx');
+const {isDownloaded, isFromGooglePlay} = require('../../components/install-scratch/install-util.js');
const ExtensionLanding = require('../../components/extension-landing/extension-landing.jsx');
const ExtensionHeader = require('../../components/extension-landing/extension-header.jsx');
@@ -16,6 +17,7 @@ const ExtensionVideo = require('../../components/extension-landing/extension-vid
const ExtensionRequirements = require('../../components/extension-landing/extension-requirements.jsx');
const ExtensionSection = require('../../components/extension-landing/extension-section.jsx');
const InstallScratchLink = require('../../components/extension-landing/install-scratch-link.jsx');
+const InstallScratch = require('../../components/install-scratch/install-scratch.jsx');
const ProjectCard = require('../../components/extension-landing/project-card.jsx');
const Steps = require('../../components/steps/steps.jsx');
@@ -57,45 +59,23 @@ class Wedo2 extends ExtensionLanding {
/>
}
renderRequirements={
-
-
-
- Windows 10 version 1709+
-
-
-
- macOS 10.13+
-
-
-
- Bluetooth 4.0
-
-
-
- Scratch Link
-
-
+
}
/>
-
+ {(isDownloaded(this.state.OS)) && (
+
+ )}
+ {(isFromGooglePlay(this.state.OS)) && (
+
+ )}
@@ -110,20 +90,25 @@ class Wedo2 extends ExtensionLanding {
/>
-
- Scratch
-
- )
- }}
- />
+ {isDownloaded(this.state.OS) && (
+
+ Scratch
+
+ )
+ }}
+ />
+ )}
+ {isFromGooglePlay(this.state.OS) && (
+
+ )}
@@ -207,32 +192,36 @@ class Wedo2 extends ExtensionLanding {
-
-
-
-
-
- ),
- macOSVersionLink: (
-
-
-
- )
- }}
- />
-
+ {isDownloaded(this.state.OS) && (
+
+
+
+
+
+
+ ),
+ macOSVersionLink: (
+
+
+
+ )
+ }}
+ />
+
+
+ )}
diff --git a/static/images/badges/google-play-badge.png b/static/images/badges/google-play-badge.png
new file mode 100644
index 000000000..27392d217
Binary files /dev/null and b/static/images/badges/google-play-badge.png differ
diff --git a/static/images/scratchlink/mac-store-badge.svg b/static/images/badges/mac-store-badge.svg
similarity index 100%
rename from static/images/scratchlink/mac-store-badge.svg
rename to static/images/badges/mac-store-badge.svg
diff --git a/static/images/scratchlink/windows-store-badge.svg b/static/images/badges/windows-store-badge.svg
similarity index 100%
rename from static/images/scratchlink/windows-store-badge.svg
rename to static/images/badges/windows-store-badge.svg
diff --git a/static/images/ev3/chromeos-enter-passcode.png b/static/images/ev3/chromeos-enter-passcode.png
new file mode 100644
index 000000000..0177172ec
Binary files /dev/null and b/static/images/ev3/chromeos-enter-passcode.png differ
diff --git a/static/images/microbit/chromeos-copy-hex.png b/static/images/microbit/chromeos-copy-hex.png
new file mode 100644
index 000000000..8e6ad6115
Binary files /dev/null and b/static/images/microbit/chromeos-copy-hex.png differ
diff --git a/static/svgs/download/mac-badge.svg b/static/svgs/download/mac-badge.svg
new file mode 100755
index 000000000..072b425a1
--- /dev/null
+++ b/static/svgs/download/mac-badge.svg
@@ -0,0 +1,46 @@
+
diff --git a/static/svgs/download/ms-badge.svg b/static/svgs/download/ms-badge.svg
new file mode 100755
index 000000000..21c139edd
--- /dev/null
+++ b/static/svgs/download/ms-badge.svg
@@ -0,0 +1,82 @@
+
+
+
diff --git a/static/svgs/extensions/android.svg b/static/svgs/extensions/android.svg
new file mode 100644
index 000000000..2a13660a3
--- /dev/null
+++ b/static/svgs/extensions/android.svg
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/static/svgs/extensions/chromeos.svg b/static/svgs/extensions/chromeos.svg
new file mode 100644
index 000000000..a26c55c29
--- /dev/null
+++ b/static/svgs/extensions/chromeos.svg
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/test/unit/components/email-step.test.jsx b/test/unit/components/email-step.test.jsx
index b49ae09c6..73a84e857 100644
--- a/test/unit/components/email-step.test.jsx
+++ b/test/unit/components/email-step.test.jsx
@@ -50,6 +50,7 @@ describe('EmailStep test', () => {
expect(emailInputWrapper.props().onSetRef).toEqual(formikWrapper.instance().handleSetEmailRef);
expect(emailInputWrapper.props().validate).toEqual(formikWrapper.instance().validateEmail);
});
+
test('props sent to FormikCheckbox for subscribe', () => {
const wrapper = shallowWithIntl();
// Dive to get past the intl wrapper
@@ -63,6 +64,7 @@ describe('EmailStep test', () => {
expect(checkboxWrapper.first().props().label).toEqual('registration.receiveEmails');
expect(checkboxWrapper.first().props().name).toEqual('subscribe');
});
+
test('handleValidSubmit passes formData to next step', () => {
const formikBag = {
setSubmitting: jest.fn()
@@ -82,6 +84,7 @@ describe('EmailStep test', () => {
expect(formikBag.setSubmitting).toHaveBeenCalledWith(false);
expect(global.grecaptcha.execute).toHaveBeenCalled();
});
+
test('captchaSolved sets token and goes to next step', () => {
const props = {
onNextStep: jest.fn()
@@ -116,6 +119,38 @@ describe('EmailStep test', () => {
}));
expect(formikBag.setSubmitting).toHaveBeenCalledWith(true);
});
+
+ test('onCaptchaError calls error function with correct message', () => {
+ const props = {
+ onRegistrationError: jest.fn()
+ };
+
+ const wrapper = shallowWithIntl(
+ );
+
+ const formikWrapper = wrapper.dive();
+ formikWrapper.instance().onCaptchaError();
+ expect(props.onRegistrationError).toHaveBeenCalledWith('registation.troubleReload');
+ });
+
+ test('Captcha load error calls error function', () => {
+ const props = {
+ onRegistrationError: jest.fn()
+ };
+ // Set this to null to force an error.
+ global.grecaptcha = null;
+ const wrapper = shallowWithIntl(
+ );
+
+ const formikWrapper = wrapper.dive();
+ formikWrapper.instance().onCaptchaLoad();
+ expect(props.onRegistrationError).toHaveBeenCalledWith('registation.troubleReload');
+ });
+
test('validateEmail test email empty', () => {
const wrapper = shallowWithIntl(
);
@@ -123,6 +158,7 @@ describe('EmailStep test', () => {
const val = formikWrapper.instance().validateEmail('');
expect(val).toBe('general.required');
});
+
test('validateEmail test email null', () => {
const wrapper = shallowWithIntl(
);
@@ -130,6 +166,7 @@ describe('EmailStep test', () => {
const val = formikWrapper.instance().validateEmail(null);
expect(val).toBe('general.required');
});
+
test('validateEmail test email undefined', () => {
const wrapper = shallowWithIntl(
);
diff --git a/test/unit/components/join-flow.test.jsx b/test/unit/components/join-flow.test.jsx
index 80dfe5a14..04cb1a6dc 100644
--- a/test/unit/components/join-flow.test.jsx
+++ b/test/unit/components/join-flow.test.jsx
@@ -126,6 +126,19 @@ describe('JoinFlow', () => {
expect(joinFlowInstance.props.refreshSession).not.toHaveBeenCalled();
expect(joinFlowInstance.state.registrationError).toBe('registration.generalError (400)');
});
+ test('handleRegistrationError with no message ', () => {
+ const joinFlowInstance = getJoinFlowWrapper().instance();
+ joinFlowInstance.setState({});
+ joinFlowInstance.handleRegistrationError();
+ expect(joinFlowInstance.state.registrationError).toBe('registration.generalError');
+ });
+
+ test('handleRegistrationError with message ', () => {
+ const joinFlowInstance = getJoinFlowWrapper().instance();
+ joinFlowInstance.setState({});
+ joinFlowInstance.handleRegistrationError('my message');
+ expect(joinFlowInstance.state.registrationError).toBe('my message');
+ });
test('handleAdvanceStep', () => {
const joinFlowInstance = getJoinFlowWrapper().instance();
diff --git a/test/unit/components/modal.test.jsx b/test/unit/components/modal.test.jsx
new file mode 100644
index 000000000..6c427dc2f
--- /dev/null
+++ b/test/unit/components/modal.test.jsx
@@ -0,0 +1,34 @@
+const React = require('react');
+const {shallowWithIntl} = require('../../helpers/intl-helpers.jsx');
+const Modal = require('../../../src/components/modal/base/modal.jsx');
+
+describe('Modal', () => {
+ test('Close button not shown when showCloseButton false', () => {
+ const showClose = true;
+ const component = shallowWithIntl(
+
+ );
+ expect(component.find('div.modal-content-close').exists()).toBe(true);
+ expect(component.find('img.modal-content-close-img').exists()).toBe(true);
+ });
+ test('Close button shown by default', () => {
+ const component = shallowWithIntl(
+
+ );
+ expect(component.find('div.modal-content-close').exists()).toBe(true);
+ expect(component.find('img.modal-content-close-img').exists()).toBe(true);
+ });
+
+ test('Close button shown when showCloseButton true', () => {
+ const showClose = false;
+ const component = shallowWithIntl(
+
+ );
+ expect(component.find('div.modal-content-close').exists()).toBe(false);
+ expect(component.find('img.modal-content-close-img').exists()).toBe(false);
+ });
+});