mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-26 17:16:11 -05:00
Merge pull request #3149 from LLK/develop
Merge develop into release branch 2019-07-18
This commit is contained in:
commit
cae62e8d72
28 changed files with 16048 additions and 309 deletions
12
.travis.yml
12
.travis.yml
|
@ -98,17 +98,23 @@ addons:
|
|||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
before_install:
|
||||
# package-lock.json was introduced in npm@5
|
||||
- '[[ $(node -v) =~ ^v9.*$ ]] || npm install -g npm@latest' # skipped when using node 9
|
||||
- npm install -g greenkeeper-lockfile
|
||||
install:
|
||||
- sudo -H pip install -r requirements.txt
|
||||
- npm --production=false install
|
||||
- npm --production=false update
|
||||
before_script: greenkeeper-lockfile-update
|
||||
after_script: greenkeeper-lockfile-upload
|
||||
jobs:
|
||||
include:
|
||||
- stage: test
|
||||
deploy:
|
||||
- provider: script
|
||||
skip_cleanup: $SKIP_CLEANUP
|
||||
script: env make sync
|
||||
script: npm run deploy
|
||||
on:
|
||||
repo: LLK/scratch-www
|
||||
branch:
|
||||
|
@ -117,7 +123,7 @@ jobs:
|
|||
- release/*
|
||||
- provider: script
|
||||
skip_cleanup: $SKIP_CLEANUP
|
||||
script: env make sync
|
||||
script: npm run deploy
|
||||
on:
|
||||
repo: LLK/scratch-www
|
||||
branch:
|
||||
|
@ -127,7 +133,7 @@ jobs:
|
|||
- cd test/integration
|
||||
- npm install
|
||||
- cd -
|
||||
script: npm run smoke-sauce
|
||||
script: npm run test:smoke:sauce
|
||||
stages:
|
||||
- test
|
||||
- name: smoke
|
||||
|
|
10
Dockerfile
10
Dockerfile
|
@ -1,10 +0,0 @@
|
|||
FROM node:8
|
||||
|
||||
RUN mkdir -p /var/app/current
|
||||
WORKDIR /var/app/current
|
||||
COPY . ./
|
||||
RUN rm -rf ./node_modules
|
||||
RUN npm install
|
||||
|
||||
EXPOSE 8333
|
||||
|
96
Makefile
96
Makefile
|
@ -1,96 +0,0 @@
|
|||
ESLINT=./node_modules/.bin/eslint
|
||||
NODE= NODE_OPTIONS=--max_old_space_size=8000 node
|
||||
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:
|
||||
@make clean
|
||||
@make translations
|
||||
@make webpack
|
||||
|
||||
clean:
|
||||
rm -rf ./build
|
||||
rm -rf ./intl
|
||||
mkdir -p build
|
||||
mkdir -p intl
|
||||
|
||||
deploy:
|
||||
@make build
|
||||
@make sync
|
||||
|
||||
translations:
|
||||
./bin/get-localized-urls localized-urls.json
|
||||
./bin/build-locales node_modules/scratch-l10n/www intl
|
||||
|
||||
webpack:
|
||||
$(WEBPACK) --bail
|
||||
|
||||
sync-s3:
|
||||
$(S3CMD) --exclude '.DS_Store' --exclude '*.svg' --exclude '*.js' ./build/ s3://$(S3_BUCKET_NAME)/
|
||||
$(S3CMD) --exclude '*' --include '*.svg' --mime-type 'image/svg+xml' ./build/ s3://$(S3_BUCKET_NAME)/
|
||||
$(S3CMD) --exclude '*' --include '*.js' --mime-type 'application/javascript' ./build/ s3://$(S3_BUCKET_NAME)/
|
||||
|
||||
sync-fastly:
|
||||
$(NODE) ./bin/configure-fastly.js
|
||||
|
||||
sync:
|
||||
@make sync-s3
|
||||
@make sync-fastly
|
||||
|
||||
# ------------------------------------
|
||||
|
||||
start:
|
||||
$(NODE) ./dev-server/index.js
|
||||
|
||||
# ------------------------------------
|
||||
|
||||
test:
|
||||
@make lint
|
||||
@make build
|
||||
@make tap
|
||||
|
||||
lint:
|
||||
$(ESLINT) . --ext .js,.jsx,.json
|
||||
|
||||
unit:
|
||||
$(TAP) ./test/unit/*.js --no-coverage -R classic
|
||||
|
||||
integration:
|
||||
$(TAP) ./test/integration/*.js --no-coverage -R classic
|
||||
|
||||
smoke:
|
||||
$(TAP) ./test/integration/smoke-testing/*.js --timeout=3600 --no-coverage -R classic
|
||||
|
||||
smoke-verbose:
|
||||
$(TAP) ./test/integration/smoke-testing/*.js --timeout=3600 -R spec --no-coverage -R classic
|
||||
|
||||
localization:
|
||||
$(TAP) ./test/localization/*.js --no-coverage -R classic
|
||||
|
||||
tap:
|
||||
$(TAP) ./test/{unit,localization}/*.js --no-coverage -R classic
|
||||
|
||||
coverage:
|
||||
$(TAP) ./test/{unit,localization}/*.js --coverage --coverage-report=lcov
|
||||
|
||||
# ------------------------------------
|
||||
|
||||
.PHONY: build clean deploy translations webpack start test lint unit functional integration localization tap coverage
|
34
README.md
34
README.md
|
@ -109,7 +109,7 @@ npm install
|
|||
virtualenv ENV
|
||||
. ENV/bin/activate
|
||||
pip install -r requirements.txt
|
||||
make deploy
|
||||
npm run build && npm run deploy
|
||||
```
|
||||
|
||||
| Variable | Default | Description |
|
||||
|
@ -138,35 +138,3 @@ Additionally, if you set `FALLBACK=https://scratch.mit.edu`, be aware that click
|
|||
|
||||
#### Windows
|
||||
Some users have experienced difficulties when trying to get our web client to work on Windows. One solution could be to use [Cygwin](https://www.cygwin.com/). If that doesn't work, you might want to use [Wubi](https://wiki.ubuntu.com/WubiGuide) (Windows XP, Vista, 7) or [Wubiuefi](https://github.com/hakuna-m/wubiuefi) (Windows 8 or higher). Wubi(uefi) is a Windows Installer for Ubuntu that allows you to have Ubuntu and Windows on one disk, without the need of an extra partition.
|
||||
|
||||
#### Docker
|
||||
|
||||
_This section is only relevant to the Scratch Team since it requires access to private repositories, so is not usable by 3rd party contributors._
|
||||
|
||||
A set of [Docker](https://www.docker.com/what-docker) related files are provided to create isolated [container](https://www.docker.com/what-container) environments suitable for end-to-end local development:
|
||||
|
||||
* Dockerfile
|
||||
* docker-compose.yml
|
||||
* docker_entrypoint.sh
|
||||
|
||||
##### Docker Quick Start (CLI)
|
||||
|
||||
Make sure you already have the Scratch REST API running locally in its docker environment.
|
||||
|
||||
```
|
||||
$ docker-compose up
|
||||
```
|
||||
|
||||
After this has launched you will be able to access a running copy of `scratch-www` on port 8333 via `http://localhost:8333`
|
||||
|
||||
##### Docker Configuration
|
||||
|
||||
`Dockerfile` defines how a `scratch-www` docker image is created.
|
||||
|
||||
`docker-compose.yml` takes care of launching `scratch-www` into a development environment that is composed of other components, such as the Scratch REST API server and the legacy Scratch code. If you have not already setup the Scratch REST API in your local environment, you will need to modify `docker-compose.yml` by removing `external: true` from:
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
scratchapi_scratch_network:
|
||||
external: true
|
||||
```
|
||||
|
|
|
@ -37,7 +37,7 @@ async.auto({
|
|||
}
|
||||
});
|
||||
},
|
||||
recvCustomVCL: ['version', function (cb, results) {
|
||||
recvCustomVCL: ['version', function (results, cb) {
|
||||
// For all the routes in routes.json, construct a varnish-style regex that matches
|
||||
// on any of those route conditions.
|
||||
var notPassStatement = fastlyConfig.getAppRouteCondition('../build/*', routes, extraAppRoutes, __dirname);
|
||||
|
@ -83,14 +83,14 @@ async.auto({
|
|||
cb
|
||||
);
|
||||
}],
|
||||
fetchCustomVCL: ['version', function (cb, results) {
|
||||
fetchCustomVCL: ['version', function (results, cb) {
|
||||
var passStatement = fastlyConfig.negateConditionStatement(
|
||||
fastlyConfig.getAppRouteCondition('../build/*', routes, extraAppRoutes, __dirname)
|
||||
);
|
||||
var ttlCondition = fastlyConfig.setResponseTTL(passStatement);
|
||||
fastly.setCustomVCL(results.version, 'fetch-condition', ttlCondition, cb);
|
||||
}],
|
||||
appRouteRequestConditions: ['version', function (cb, results) {
|
||||
appRouteRequestConditions: ['version', function (results, cb) {
|
||||
var conditions = {};
|
||||
async.forEachOf(routes, function (route, id, cb2) {
|
||||
var condition = {
|
||||
|
@ -110,7 +110,7 @@ async.auto({
|
|||
cb(null, conditions);
|
||||
});
|
||||
}],
|
||||
appRouteHeaders: ['version', 'appRouteRequestConditions', function (cb, results) {
|
||||
appRouteHeaders: ['version', 'appRouteRequestConditions', function (results, cb) {
|
||||
var headers = {};
|
||||
async.forEachOf(routes, function (route, id, cb2) {
|
||||
if (route.redirect) {
|
||||
|
@ -133,7 +133,7 @@ async.auto({
|
|||
};
|
||||
fastly.setResponseObject(results.version, responseObject, cb3);
|
||||
},
|
||||
redirectHeader: ['responseCondition', function (cb3, redirectResults) {
|
||||
redirectHeader: ['responseCondition', function (redirectResults, cb3) {
|
||||
var header = {
|
||||
name: fastlyConfig.getHeaderNameForRoute(route),
|
||||
action: 'set',
|
||||
|
@ -172,7 +172,7 @@ async.auto({
|
|||
cb(null, headers);
|
||||
});
|
||||
}],
|
||||
tipbarRedirectHeaders: ['version', function (cb, results) {
|
||||
tipbarRedirectHeaders: ['version', function (results, cb) {
|
||||
async.auto({
|
||||
requestCondition: function (cb2) {
|
||||
var condition = {
|
||||
|
@ -192,7 +192,7 @@ async.auto({
|
|||
};
|
||||
fastly.setCondition(results.version, condition, cb2);
|
||||
},
|
||||
responseObject: ['requestCondition', function (cb2, redirectResults) {
|
||||
responseObject: ['requestCondition', function (redirectResults, cb2) {
|
||||
var responseObject = {
|
||||
name: 'redirects/?tip_bar=',
|
||||
status: 301,
|
||||
|
@ -201,7 +201,7 @@ async.auto({
|
|||
};
|
||||
fastly.setResponseObject(results.version, responseObject, cb2);
|
||||
}],
|
||||
redirectHeader: ['responseCondition', function (cb2, redirectResults) {
|
||||
redirectHeader: ['responseCondition', function (redirectResults, cb2) {
|
||||
var header = {
|
||||
name: 'redirects/?tip_bar=',
|
||||
action: 'set',
|
||||
|
@ -218,7 +218,7 @@ async.auto({
|
|||
cb(null, redirectResults);
|
||||
});
|
||||
}],
|
||||
embedRedirectHeaders: ['version', function (cb, results) {
|
||||
embedRedirectHeaders: ['version', function (results, cb) {
|
||||
async.auto({
|
||||
requestCondition: function (cb2) {
|
||||
var condition = {
|
||||
|
@ -238,7 +238,7 @@ async.auto({
|
|||
};
|
||||
fastly.setCondition(results.version, condition, cb2);
|
||||
},
|
||||
responseObject: ['requestCondition', function (cb2, redirectResults) {
|
||||
responseObject: ['requestCondition', function (redirectResults, cb2) {
|
||||
var responseObject = {
|
||||
name: 'redirects/projects/embed',
|
||||
status: 301,
|
||||
|
@ -247,7 +247,7 @@ async.auto({
|
|||
};
|
||||
fastly.setResponseObject(results.version, responseObject, cb2);
|
||||
}],
|
||||
redirectHeader: ['responseCondition', function (cb2, redirectResults) {
|
||||
redirectHeader: ['responseCondition', function (redirectResults, cb2) {
|
||||
var header = {
|
||||
name: 'redirects/projects/embed',
|
||||
action: 'set',
|
||||
|
|
|
@ -22,15 +22,6 @@ routes.forEach(route => {
|
|||
});
|
||||
|
||||
var middlewareOptions = {};
|
||||
if (process.env.USE_DOCKER_WATCHOPTIONS) {
|
||||
middlewareOptions = {
|
||||
watchOptions: {
|
||||
aggregateTimeout: 500,
|
||||
poll: 2500,
|
||||
ignored: ['node_modules', 'build']
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
app.use(webpackDevMiddleware(compiler, middlewareOptions));
|
||||
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
version: '3.4'
|
||||
volumes:
|
||||
npm_data:
|
||||
runtime_data:
|
||||
|
||||
networks:
|
||||
default:
|
||||
external:
|
||||
name: scratchapi_scratch_network
|
||||
|
||||
services:
|
||||
app:
|
||||
container_name: scratch-www-app
|
||||
hostname: scratch-www-app
|
||||
environment:
|
||||
- API_HOST=http://localhost:8491
|
||||
- FALLBACK=http://scratchr2-app:8080
|
||||
- USE_DOCKER_WATCHOPTIONS=true
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: Dockerfile
|
||||
image: scratch-www:latest
|
||||
command: ./docker_entrypoint.sh npm start
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ./
|
||||
target: /var/app/current
|
||||
volume:
|
||||
nocopy: true
|
||||
- type: bind
|
||||
source: ../scratch-gui
|
||||
target: /var/app/current/scratch-gui
|
||||
volume:
|
||||
nocopy: true
|
||||
- npm_data:/var/app/current/node_modules
|
||||
- runtime_data:/runtime
|
||||
ports:
|
||||
- "8333:8333"
|
|
@ -1,11 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
echo "App Entrypoint"
|
||||
|
||||
if [ ! -f /runtime/.translations ]; then
|
||||
echo "Generating intl/translations"
|
||||
make translations
|
||||
touch /runtime/.translations
|
||||
fi
|
||||
|
||||
exec "$@"
|
15727
package-lock.json
generated
Normal file
15727
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
41
package.json
41
package.json
|
@ -3,15 +3,23 @@
|
|||
"version": "1.0.0",
|
||||
"description": "Standalone WWW client for Scratch",
|
||||
"scripts": {
|
||||
"start": "make start",
|
||||
"stop": "make stop",
|
||||
"test": "make test",
|
||||
"smoke": "tap ./test/integration/smoke-testing/*.js --timeout=3600 --no-coverage -R classic",
|
||||
"smoke-verbose": "tap ./test/integration/smoke-testing/*.js --timeout=3600 --no-coverage -R spec",
|
||||
"smoke-sauce": "SMOKE_REMOTE=true tap ./test/integration/smoke-testing/*.js --timeout=60000 --no-coverage -R classic",
|
||||
"watch": "make watch",
|
||||
"build": "make build",
|
||||
"dev": "make watch && make start &",
|
||||
"start": "node ./dev-server/index.js",
|
||||
"test": "npm run test:lint && npm run build && npm run test:tap",
|
||||
"test:lint": "eslint . --ext .js,.jsx,.json",
|
||||
"test:smoke": "tap ./test/integration/smoke-testing/*.js --timeout=3600 --no-coverage -R classic",
|
||||
"test:smoke:verbose": "tap ./test/integration/smoke-testing/*.js --timeout=3600 --no-coverage -R spec",
|
||||
"test:smoke:sauce": "SMOKE_REMOTE=true tap ./test/integration/smoke-testing/*.js --timeout=60000 --no-coverage -R classic",
|
||||
"test:tap": "tap ./test/{unit,localization}/*.js --no-coverage -R classic",
|
||||
"test:coverage": "tap ./test/{unit,localization}/*.js --coverage --coverage-report=lcov",
|
||||
"build": "npm run clean && npm run translate && webpack --bail",
|
||||
"clean": "rm -rf ./build && rm -rf ./intl && mkdir -p build && mkdir -p intl",
|
||||
"deploy": "npm run deploy:s3 && npm run deploy:fastly",
|
||||
"deploy:fastly": "node ./bin/configure-fastly.js",
|
||||
"deploy:s3": "npm run deploy:s3:all && npm run deploy:s3:svg && npm run deploy:s3:js",
|
||||
"deploy:s3cmd": "s3cmd sync -P --delete-removed --add-header=Cache-Control:no-cache,public,max-age=3600",
|
||||
"deploy:s3:all": "npm run deploy:s3cmd -- --exclude '.DS_Store' --exclude '*.svg' --exclude '*.js' ./build/ s3://$S3_BUCKET_NAME/",
|
||||
"deploy:s3:svg": "npm run deploy:s3cmd -- --exclude '*' --include '*.svg' --mime-type 'image/svg+xml' ./build/ s3://$S3_BUCKET_NAME/",
|
||||
"deploy:s3:js": "npm run deploy:s3cmd -- --exclude '*' --include '*.js' --mime-type 'application/javascript' ./build/ s3://$S3_BUCKET_NAME/",
|
||||
"translate:urls": "node ./bin/get-localized-urls localized-urls.json",
|
||||
"translate:files": "node ./bin/build-locales node_modules/scratch-l10n/www intl",
|
||||
"translate": "npm run translate:urls && npm run translate:files"
|
||||
|
@ -36,13 +44,12 @@
|
|||
"lodash.defaults": "4.0.1",
|
||||
"newrelic": "1.25.4",
|
||||
"react-helmet": "5.2.0",
|
||||
"scratch-docker": "^1.0.2",
|
||||
"scratch-parser": "^5.0.0",
|
||||
"scratch-storage": "^0.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ajv": "6.4.0",
|
||||
"async": "1.5.2",
|
||||
"async": "3.1.0",
|
||||
"autoprefixer": "6.3.6",
|
||||
"babel-cli": "6.26.0",
|
||||
"babel-core": "6.23.1",
|
||||
|
@ -65,13 +72,13 @@
|
|||
"eslint-plugin-react": "7.4.0",
|
||||
"exenv": "1.2.0",
|
||||
"fastly": "1.2.1",
|
||||
"file-loader": "0.8.4",
|
||||
"file-loader": "4.0.0",
|
||||
"formik": "1.5.4",
|
||||
"formsy-react": "1.1.4",
|
||||
"formsy-react-components": "1.0.0",
|
||||
"git-bundle-sha": "0.0.2",
|
||||
"glob": "5.0.15",
|
||||
"google-libphonenumber": "1.0.21",
|
||||
"google-libphonenumber": "3.2.3",
|
||||
"html-webpack-plugin": "2.22.0",
|
||||
"iso-3166-2": "0.4.0",
|
||||
"json-loader": "0.5.2",
|
||||
|
@ -84,7 +91,7 @@
|
|||
"lodash.merge": "3.3.2",
|
||||
"lodash.mergewith": "4.6.1",
|
||||
"lodash.omit": "3.1.0",
|
||||
"lodash.range": "3.0.1",
|
||||
"lodash.range": "3.2.0",
|
||||
"lodash.uniqby": "4.7.0",
|
||||
"minilog": "2.0.8",
|
||||
"node-dir": "0.1.16",
|
||||
|
@ -96,7 +103,7 @@
|
|||
"react": "16.2.0",
|
||||
"react-dom": "16.2.0",
|
||||
"react-intl": "2.8.0",
|
||||
"react-modal": "3.4.1",
|
||||
"react-modal": "3.8.2",
|
||||
"react-onclickoutside": "6.7.1",
|
||||
"react-redux": "5.0.7",
|
||||
"react-responsive": "3.0.0",
|
||||
|
@ -106,13 +113,13 @@
|
|||
"redux": "3.5.2",
|
||||
"redux-thunk": "2.0.1",
|
||||
"sass-loader": "6.0.6",
|
||||
"scratch-gui": "0.1.0-prerelease.20190711011201",
|
||||
"scratch-gui": "0.1.0-prerelease.20190717220516",
|
||||
"scratch-l10n": "latest",
|
||||
"slick-carousel": "1.6.0",
|
||||
"source-map-support": "0.3.2",
|
||||
"style-loader": "0.12.3",
|
||||
"tap": "14.2.0",
|
||||
"url-loader": "0.5.6",
|
||||
"url-loader": "2.0.1",
|
||||
"watch": "0.16.0",
|
||||
"webpack": "2.7.0",
|
||||
"webpack-dev-middleware": "2.0.4",
|
||||
|
|
62
src/components/formik-forms/formik-select.jsx
Normal file
62
src/components/formik-forms/formik-select.jsx
Normal file
|
@ -0,0 +1,62 @@
|
|||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
import {Field} from 'formik';
|
||||
|
||||
const ValidationMessage = require('../forms/validation-message.jsx');
|
||||
|
||||
require('../forms/select.scss');
|
||||
require('../forms/row.scss');
|
||||
|
||||
const FormikSelect = ({
|
||||
className,
|
||||
error,
|
||||
options,
|
||||
validationClassName,
|
||||
...props
|
||||
}) => {
|
||||
const optionsList = options.map((item, index) => (
|
||||
<option
|
||||
key={index}
|
||||
value={item.value}
|
||||
>
|
||||
{item.label}
|
||||
</option>
|
||||
));
|
||||
return (
|
||||
<div className="col-sm-9 row">
|
||||
<Field
|
||||
className={classNames(
|
||||
'select',
|
||||
className
|
||||
)}
|
||||
component="select"
|
||||
{...props}
|
||||
>
|
||||
{optionsList}
|
||||
</Field>
|
||||
{error && (
|
||||
<ValidationMessage
|
||||
className={validationClassName}
|
||||
message={error}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
FormikSelect.propTypes = {
|
||||
className: PropTypes.string,
|
||||
error: PropTypes.string,
|
||||
options: PropTypes.arrayOf(PropTypes.shape({
|
||||
disabled: PropTypes.bool,
|
||||
label: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired
|
||||
})).isRequired,
|
||||
validationClassName: PropTypes.string,
|
||||
// selected value
|
||||
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
|
||||
};
|
||||
|
||||
module.exports = FormikSelect;
|
|
@ -2,6 +2,10 @@ const React = require('react');
|
|||
const PropTypes = require('prop-types');
|
||||
|
||||
const NextStepButton = require('./next-step-button.jsx');
|
||||
const ModalTitle = require('../modal/base/modal-title.jsx');
|
||||
const ModalInnerContent = require('../modal/base/modal-inner-content.jsx');
|
||||
|
||||
require('./join-flow-step.scss');
|
||||
|
||||
const JoinFlowStep = ({
|
||||
children,
|
||||
|
@ -12,19 +16,20 @@ const JoinFlowStep = ({
|
|||
}) => (
|
||||
<form onSubmit={onSubmit}>
|
||||
<div>
|
||||
{title && (
|
||||
<h2>
|
||||
{title}
|
||||
</h2>
|
||||
)}
|
||||
{description && (
|
||||
<p>
|
||||
<span>
|
||||
<ModalInnerContent className="join-flow-inner-content">
|
||||
{title && (
|
||||
<ModalTitle
|
||||
className="join-flow-title"
|
||||
title={title}
|
||||
/>
|
||||
)}
|
||||
{description && (
|
||||
<div className="join-flow-description">
|
||||
{description}
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</ModalInnerContent>
|
||||
</div>
|
||||
<NextStepButton waiting={waiting} />
|
||||
</form>
|
||||
|
|
23
src/components/join-flow/join-flow-step.scss
Normal file
23
src/components/join-flow/join-flow-step.scss
Normal file
|
@ -0,0 +1,23 @@
|
|||
@import "../../colors";
|
||||
@import "../../frameless";
|
||||
|
||||
.join-flow-title {
|
||||
color: $type-gray;
|
||||
font-size: 1.875rem;
|
||||
}
|
||||
|
||||
.join-flow-description {
|
||||
font-size: .875rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1.875rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.join-flow-inner-content {
|
||||
box-shadow: none;
|
||||
width: calc(100% - 5.875rem);
|
||||
/* must use padding for top, rather than margin, because margins will collapse */
|
||||
margin: 0 auto;
|
||||
padding: 2.3125rem 0 2.5rem;
|
||||
font-size: .875rem;
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
/* eslint-disable react/no-multi-comp */
|
||||
const bindAll = require('lodash.bindall');
|
||||
const classNames = require('classnames');
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
import {Formik} from 'formik';
|
||||
|
@ -114,57 +115,70 @@ class UsernameStep extends React.Component {
|
|||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
<b>
|
||||
{this.props.intl.formatMessage({id: 'registration.createUsername'})}
|
||||
</b>
|
||||
<div className="join-flow-input-title">
|
||||
{this.props.intl.formatMessage({id: 'registration.createUsername'})}
|
||||
</div>
|
||||
<FormikInput
|
||||
className={errors.username ? 'fail' : ''}
|
||||
className={classNames(
|
||||
'join-flow-input',
|
||||
{fail: errors.username}
|
||||
)}
|
||||
error={errors.username}
|
||||
id="username"
|
||||
name="username"
|
||||
validate={this.validateUsernameIfPresent}
|
||||
onBlur={() => validateField('username')} // eslint-disable-line react/jsx-no-bind
|
||||
/>
|
||||
<b>
|
||||
{this.props.intl.formatMessage({id: 'general.password'})}
|
||||
</b>
|
||||
<div
|
||||
onClick={this.handleChangeShowPassword}
|
||||
>
|
||||
{/* TODO: should localize 'Hide password' if we use that */}
|
||||
{this.state.showPassword ? 'Hide password' : (
|
||||
this.props.intl.formatMessage({id: 'registration.showPassword'})
|
||||
)}
|
||||
<div className="join-flow-password-section">
|
||||
<div className="join-flow-input-title">
|
||||
{this.props.intl.formatMessage({id: 'general.password'})}
|
||||
</div>
|
||||
<FormikInput
|
||||
className={classNames(
|
||||
'join-flow-input',
|
||||
{fail: errors.password}
|
||||
)}
|
||||
error={errors.password}
|
||||
id="password"
|
||||
name="password"
|
||||
type={this.state.showPassword ? 'text' : 'password'}
|
||||
validate={this.validatePasswordIfPresent}
|
||||
validationClassName="validation-full-width-input"
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
onBlur={() => validateField('password')}
|
||||
/* eslint-enable react/jsx-no-bind */
|
||||
/>
|
||||
<FormikInput
|
||||
className={classNames(
|
||||
'join-flow-input',
|
||||
{fail: errors.passwordConfirm}
|
||||
)}
|
||||
error={errors.passwordConfirm}
|
||||
id="passwordConfirm"
|
||||
name="passwordConfirm"
|
||||
type={this.state.showPassword ? 'text' : 'password'}
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
validate={() =>
|
||||
this.validatePasswordConfirmIfPresent(values.password,
|
||||
values.passwordConfirm)
|
||||
}
|
||||
validationClassName="validation-full-width-input"
|
||||
onBlur={() =>
|
||||
validateField('passwordConfirm')
|
||||
}
|
||||
/* eslint-enable react/jsx-no-bind */
|
||||
/>
|
||||
<div className="join-flow-input-title">
|
||||
<div
|
||||
onClick={this.handleChangeShowPassword}
|
||||
>
|
||||
{/* TODO: should localize 'Hide password' if we use that */}
|
||||
{this.state.showPassword ? 'Hide password' : (
|
||||
this.props.intl.formatMessage({id: 'registration.showPassword'})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FormikInput
|
||||
className={errors.password ? 'fail' : ''}
|
||||
error={errors.password}
|
||||
id="password"
|
||||
name="password"
|
||||
type={this.state.showPassword ? 'text' : 'password'}
|
||||
validate={this.validatePasswordIfPresent}
|
||||
onBlur={() => validateField('password')} // eslint-disable-line react/jsx-no-bind
|
||||
/>
|
||||
<b>
|
||||
{this.props.intl.formatMessage({id: 'general.error'})}
|
||||
</b>
|
||||
<FormikInput
|
||||
className={errors.passwordConfirm ? 'fail' : ''}
|
||||
error={errors.passwordConfirm}
|
||||
id="passwordConfirm"
|
||||
name="passwordConfirm"
|
||||
type={this.state.showPassword ? 'text' : 'password'}
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
validate={() =>
|
||||
this.validatePasswordConfirmIfPresent(values.password, values.passwordConfirm)
|
||||
}
|
||||
onBlur={() =>
|
||||
validateField('passwordConfirm')
|
||||
}
|
||||
/* eslint-enable react/jsx-no-bind */
|
||||
/>
|
||||
</div>
|
||||
</JoinFlowStep>
|
||||
);
|
||||
|
|
27
src/components/join-flow/join-flow-steps.scss
Normal file
27
src/components/join-flow/join-flow-steps.scss
Normal file
|
@ -0,0 +1,27 @@
|
|||
@import "../../colors";
|
||||
@import "../../frameless";
|
||||
|
||||
.join-flow-input {
|
||||
width: 100%;
|
||||
height: 2.75rem;
|
||||
border-radius: .5rem;
|
||||
background-color: $ui-white;
|
||||
margin-bottom: .5rem;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 .25rem $ui-blue-25percent;
|
||||
}
|
||||
}
|
||||
|
||||
.join-flow-input-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
.validation-full-width-input {
|
||||
transform: translate(21.5625rem, 0);
|
||||
}
|
||||
|
||||
.join-flow-password-section {
|
||||
margin-top: 1.125rem;
|
||||
}
|
|
@ -6,6 +6,7 @@ const React = require('react');
|
|||
const injectIntl = require('../../lib/intl.jsx').injectIntl;
|
||||
const intlShape = require('../../lib/intl.jsx').intlShape;
|
||||
|
||||
const Progression = require('../progression/progression.jsx');
|
||||
const JoinFlowSteps = require('./join-flow-steps.jsx');
|
||||
|
||||
/*
|
||||
|
@ -17,6 +18,11 @@ class JoinFlow extends React.Component {
|
|||
bindAll(this, [
|
||||
'handleAdvanceStep'
|
||||
]);
|
||||
this.state = {
|
||||
formData: {},
|
||||
registrationError: null,
|
||||
step: 0
|
||||
};
|
||||
}
|
||||
handleAdvanceStep (formData) {
|
||||
formData = formData || {};
|
||||
|
@ -28,7 +34,11 @@ class JoinFlow extends React.Component {
|
|||
render () {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<JoinFlowSteps.UsernameStep />
|
||||
<Progression step={this.state.step}>
|
||||
<JoinFlowSteps.UsernameStep
|
||||
onNextStep={this.handleAdvanceStep}
|
||||
/>
|
||||
</Progression>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,19 +5,23 @@ const injectIntl = require('react-intl').injectIntl;
|
|||
|
||||
const intl = require('../../lib/intl.jsx');
|
||||
const Spinner = require('../../components/spinner/spinner.jsx');
|
||||
const ModalTitle = require('../modal/base/modal-title.jsx');
|
||||
|
||||
require('./next-step-button.scss');
|
||||
|
||||
const NextStepButton = props => (
|
||||
<button
|
||||
className="modal-flush-bottom-button"
|
||||
disabled={props.waiting}
|
||||
type="submit"
|
||||
{...omit(props, ['text', 'waiting'])}
|
||||
{...omit(props, ['intl', 'text', 'waiting'])}
|
||||
>
|
||||
{props.waiting ?
|
||||
<Spinner /> :
|
||||
(props.text ?
|
||||
props.text : (
|
||||
<intl.FormattedMessage id="registration.nextStep" />
|
||||
)
|
||||
<Spinner /> : (
|
||||
<ModalTitle
|
||||
className="next-step-title-large"
|
||||
title={props.text ? props.text : props.intl.formatMessage({id: 'registration.nextStep'})}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</button>
|
||||
|
|
15
src/components/join-flow/next-step-button.scss
Normal file
15
src/components/join-flow/next-step-button.scss
Normal file
|
@ -0,0 +1,15 @@
|
|||
@import "../../colors";
|
||||
@import "../../frameless";
|
||||
|
||||
.modal-flush-bottom-button {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
border-bottom-left-radius: 1rem;
|
||||
border-bottom-right-radius: 1rem;
|
||||
height: 5.1875rem;
|
||||
background-color: $ui-orange;
|
||||
}
|
||||
|
||||
.next-step-title-large {
|
||||
font-size: 1.25rem;
|
||||
}
|
|
@ -11,6 +11,7 @@ const Spinner = require('../../spinner/spinner.jsx');
|
|||
const FlexRow = require('../../flex-row/flex-row.jsx');
|
||||
const StudioButton = require('./studio-button.jsx');
|
||||
const ModalTitle = require('../base/modal-title.jsx');
|
||||
const ModalInnerContent = require('../base/modal-inner-content.jsx');
|
||||
|
||||
require('../../forms/button.scss');
|
||||
require('./modal.scss');
|
||||
|
@ -49,7 +50,7 @@ const AddToStudioModalPresentation = ({
|
|||
<div className="addToStudio-modal-header modal-header">
|
||||
<ModalTitle title={contentLabel} />
|
||||
</div>
|
||||
<div className="addToStudio-modal-content modal-content">
|
||||
<ModalInnerContent className="addToStudio-modal-content">
|
||||
<div className="studio-list-outer-scrollbox">
|
||||
<div className="studio-list-inner-scrollbox">
|
||||
<div className="studio-list-container">
|
||||
|
@ -101,7 +102,7 @@ const AddToStudioModalPresentation = ({
|
|||
]}
|
||||
</FlexRow>
|
||||
</Form>
|
||||
</div>
|
||||
</ModalInnerContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
|
26
src/components/modal/base/modal-inner-content.jsx
Normal file
26
src/components/modal/base/modal-inner-content.jsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./modal-inner-content.scss');
|
||||
|
||||
const ModalInnerContent = ({
|
||||
children,
|
||||
className
|
||||
}) => (
|
||||
<div
|
||||
className={classNames(
|
||||
'modal-inner-content',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
ModalInnerContent.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = ModalInnerContent;
|
9
src/components/modal/base/modal-inner-content.scss
Normal file
9
src/components/modal/base/modal-inner-content.scss
Normal file
|
@ -0,0 +1,9 @@
|
|||
@import "../../../colors";
|
||||
@import "../../../frameless";
|
||||
|
||||
.modal-inner-content {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
border-radius: 0;
|
||||
flex-direction: column;
|
||||
}
|
|
@ -16,12 +16,6 @@
|
|||
padding: 0;
|
||||
width: 48.75rem;
|
||||
|
||||
.modal-content { /* content inside of content */
|
||||
display: flex;
|
||||
border-radius: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
|
|
@ -2,4 +2,5 @@
|
|||
@import "../../../frameless";
|
||||
|
||||
.mod-join {
|
||||
width: 27.4375rem;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ const Modal = require('../base/modal.jsx');
|
|||
const classNames = require('classnames');
|
||||
|
||||
const ModalTitle = require('../base/modal-title.jsx');
|
||||
const ModalInnerContent = require('../base/modal-inner-content.jsx');
|
||||
const Form = require('../../forms/form.jsx');
|
||||
const Button = require('../../forms/button.jsx');
|
||||
const Select = require('../../forms/select.jsx');
|
||||
|
@ -130,7 +131,7 @@ class ReportModal extends React.Component {
|
|||
onValid={this.handleValid}
|
||||
onValidSubmit={onReport}
|
||||
>
|
||||
<div className="report-modal-content modal-content">
|
||||
<ModalInnerContent className="report-modal-content">
|
||||
{isConfirmed ? (
|
||||
<div className="received">
|
||||
<div className="received-header">
|
||||
|
@ -198,7 +199,7 @@ class ReportModal extends React.Component {
|
|||
<FormattedMessage id="report.error" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ModalInnerContent>
|
||||
<FlexRow className="action-buttons">
|
||||
<div className="action-buttons-overflow-fix">
|
||||
{isConfirmed ? (
|
||||
|
|
|
@ -6,6 +6,8 @@ const classNames = require('classnames');
|
|||
|
||||
const Modal = require('../base/modal.jsx');
|
||||
const ModalTitle = require('../base/modal-title.jsx');
|
||||
const ModalInnerContent = require('../base/modal-inner-content.jsx');
|
||||
|
||||
const FlexRow = require('../../flex-row/flex-row.jsx');
|
||||
|
||||
require('../../forms/button.scss');
|
||||
|
@ -37,7 +39,7 @@ const SocialModalPresentation = ({
|
|||
<div className="social-modal-header modal-header">
|
||||
<ModalTitle title={intl.formatMessage({id: 'general.copyLink'})} />
|
||||
</div>
|
||||
<div className="modal-content social-modal-content">
|
||||
<ModalInnerContent className="social-modal-content">
|
||||
|
||||
{/* top row: link */}
|
||||
<div className="link-section">
|
||||
|
@ -111,7 +113,7 @@ const SocialModalPresentation = ({
|
|||
</FlexRow>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ModalInnerContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const defaults = require('lodash.defaults');
|
||||
const keyMirror = require('keymirror');
|
||||
const async = require('async');
|
||||
const eachLimit = require('async/eachLimit');
|
||||
const mergeWith = require('lodash.mergewith');
|
||||
const uniqBy = require('lodash.uniqby');
|
||||
|
||||
|
@ -524,7 +524,7 @@ module.exports.getCommentById = (projectId, commentId, ownerUsername, isAdmin, t
|
|||
module.exports.getReplies = (projectId, commentIds, offset, ownerUsername, isAdmin, token) => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('replies', module.exports.Status.FETCHING));
|
||||
const fetchedReplies = {};
|
||||
async.eachLimit(commentIds, 10, (parentId, callback) => {
|
||||
eachLimit(commentIds, 10, (parentId, callback) => {
|
||||
api({
|
||||
uri: `${isAdmin ? '/admin' : `/users/${ownerUsername}`}/projects/${projectId}/comments/${parentId}/replies`,
|
||||
authentication: token ? token : null,
|
||||
|
|
|
@ -12,10 +12,10 @@
|
|||
* Tests can be run using Saucelabs, an online service that can test browser/os combinations remotely. Currently all tests are written for use for chrome on mac.
|
||||
|
||||
## Using tap
|
||||
* Run all tests in the smoke-testing directory from the command-line: `$ SMOKE_USERNAME=username SMOKE_PASSWORD=password ROOT_URL=https://scratch.mit.edu npm run smoke`
|
||||
* Run all tests in the smoke-testing directory from the command-line: `$ SMOKE_USERNAME=username SMOKE_PASSWORD=password ROOT_URL=https://scratch.mit.edu npm run test:smoke`
|
||||
* To run a single file from the command-line: `$ SMOKE_USERNAME=username SMOKE_PASSWORD=password ROOT_URL=https://scratch.mit.edu node_modules/.bin/tap ./test/integration/smoke-testing/filename.js --timeout=3600`
|
||||
* The timeout var is for the length of the entire tap test-suite; if you are getting a timeout error, you may need to adjust this value (some of the Selenium tests take a while to run)
|
||||
* To run tests using saucelabs run this command `$ SMOKE_USERNAME=username SMOKE_PASSWORD=password SAUCE_USERNAME=saucelabsUsername SAUCE_ACCESS_KEY=saucelabsAccessKey ROOT_URL=https://scratch.mit.edu npm run smoke-sauce`
|
||||
* To run tests using saucelabs run this command `$ SMOKE_USERNAME=username SMOKE_PASSWORD=password SAUCE_USERNAME=saucelabsUsername SAUCE_ACCESS_KEY=saucelabsAccessKey ROOT_URL=https://scratch.mit.edu npm run test:smoke:sauce`
|
||||
|
||||
|
||||
### Configuration
|
||||
|
@ -25,7 +25,7 @@
|
|||
| `ROOT_URL` | `scratch.ly` | Location you want to run the tests against |
|
||||
| `SMOKE_USERNAME` | `None` | Username for Scratch user you're signing in with to test |
|
||||
| `SMOKE_PASSWORD` | `None` | Password for Scratch user you're signing in with to test |
|
||||
| `SMOKE_REMOTE` | `false` | Tests with Sauce Labs or not. True if running smoke-sauce |
|
||||
| `SMOKE_REMOTE` | `false` | Tests with Sauce Labs or not. True if running test:smoke:sauce |
|
||||
| `SMOKE_HEADLESS` | `false` | Run browser in headless mode. Flaky at the moment |
|
||||
| `SAUCE_USERNAME` | `None` | Username for your Sauce Labs account |
|
||||
| `SAUCE_ACCESS_KEY` | `None` | Access Key for Sauce Labs found under User Settings |
|
||||
|
|
|
@ -81,7 +81,8 @@ module.exports = {
|
|||
include: [
|
||||
path.resolve(__dirname, 'src'),
|
||||
/node_modules[\\/]scratch-[^\\/]+[\\/]src/,
|
||||
/node_modules[\\/]pify/
|
||||
/node_modules[\\/]pify/,
|
||||
/node_modules[\\/]async/
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue