Merge pull request #3783 from LLK/release/2020-04-02

[Master] Release 2020-04-02
This commit is contained in:
Eric Rosenbaum 2020-04-02 11:42:16 -04:00 committed by GitHub
commit aa31c2ed57
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 695 additions and 763 deletions

944
package-lock.json generated
View file

@ -4,6 +4,475 @@
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@babel/cli": {
"version": "7.8.4",
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.8.4.tgz",
"integrity": "sha512-XXLgAm6LBbaNxaGhMAznXXaxtCWfuv6PIDJ9Alsy9JYTOh+j2jJz+L/162kkfU1j/pTSxK1xGmlwI4pdIMkoag==",
"dev": true,
"requires": {
"chokidar": "^2.1.8",
"commander": "^4.0.1",
"convert-source-map": "^1.1.0",
"fs-readdir-recursive": "^1.1.0",
"glob": "^7.0.0",
"lodash": "^4.17.13",
"make-dir": "^2.1.0",
"slash": "^2.0.0",
"source-map": "^0.5.0"
},
"dependencies": {
"anymatch": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
"integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
"dev": true,
"optional": true,
"requires": {
"micromatch": "^3.1.4",
"normalize-path": "^2.1.1"
},
"dependencies": {
"normalize-path": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
"integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
"dev": true,
"optional": true,
"requires": {
"remove-trailing-separator": "^1.0.1"
}
}
}
},
"arr-diff": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
"integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
"dev": true,
"optional": true
},
"array-unique": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
"dev": true,
"optional": true
},
"braces": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
"dev": true,
"optional": true,
"requires": {
"arr-flatten": "^1.1.0",
"array-unique": "^0.3.2",
"extend-shallow": "^2.0.1",
"fill-range": "^4.0.0",
"isobject": "^3.0.1",
"repeat-element": "^1.1.2",
"snapdragon": "^0.8.1",
"snapdragon-node": "^2.0.1",
"split-string": "^3.0.2",
"to-regex": "^3.0.1"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"optional": true,
"requires": {
"is-extendable": "^0.1.0"
}
}
}
},
"chokidar": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
"integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
"dev": true,
"optional": true,
"requires": {
"anymatch": "^2.0.0",
"async-each": "^1.0.1",
"braces": "^2.3.2",
"fsevents": "^1.2.7",
"glob-parent": "^3.1.0",
"inherits": "^2.0.3",
"is-binary-path": "^1.0.0",
"is-glob": "^4.0.0",
"normalize-path": "^3.0.0",
"path-is-absolute": "^1.0.0",
"readdirp": "^2.2.1",
"upath": "^1.1.1"
}
},
"commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"dev": true
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"optional": true,
"requires": {
"ms": "2.0.0"
}
},
"expand-brackets": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
"integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
"dev": true,
"optional": true,
"requires": {
"debug": "^2.3.3",
"define-property": "^0.2.5",
"extend-shallow": "^2.0.1",
"posix-character-classes": "^0.1.0",
"regex-not": "^1.0.0",
"snapdragon": "^0.8.1",
"to-regex": "^3.0.1"
},
"dependencies": {
"define-property": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
"dev": true,
"optional": true,
"requires": {
"is-descriptor": "^0.1.0"
}
},
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"optional": true,
"requires": {
"is-extendable": "^0.1.0"
}
},
"is-accessor-descriptor": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
"integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
"dev": true,
"optional": true,
"requires": {
"kind-of": "^3.0.2"
},
"dependencies": {
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"optional": true,
"requires": {
"is-buffer": "^1.1.5"
}
}
}
},
"is-data-descriptor": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
"integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
"dev": true,
"optional": true,
"requires": {
"kind-of": "^3.0.2"
},
"dependencies": {
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"optional": true,
"requires": {
"is-buffer": "^1.1.5"
}
}
}
},
"is-descriptor": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
"integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
"dev": true,
"optional": true,
"requires": {
"is-accessor-descriptor": "^0.1.6",
"is-data-descriptor": "^0.1.4",
"kind-of": "^5.0.0"
}
},
"kind-of": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
"integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
"dev": true,
"optional": true
}
}
},
"extglob": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
"integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
"dev": true,
"optional": true,
"requires": {
"array-unique": "^0.3.2",
"define-property": "^1.0.0",
"expand-brackets": "^2.1.4",
"extend-shallow": "^2.0.1",
"fragment-cache": "^0.2.1",
"regex-not": "^1.0.0",
"snapdragon": "^0.8.1",
"to-regex": "^3.0.1"
},
"dependencies": {
"define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
"dev": true,
"optional": true,
"requires": {
"is-descriptor": "^1.0.0"
}
},
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"optional": true,
"requires": {
"is-extendable": "^0.1.0"
}
}
}
},
"fill-range": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
"dev": true,
"optional": true,
"requires": {
"extend-shallow": "^2.0.1",
"is-number": "^3.0.0",
"repeat-string": "^1.6.1",
"to-regex-range": "^2.1.0"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"optional": true,
"requires": {
"is-extendable": "^0.1.0"
}
}
}
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"glob-parent": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
"integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
"dev": true,
"optional": true,
"requires": {
"is-glob": "^3.1.0",
"path-dirname": "^1.0.0"
},
"dependencies": {
"is-glob": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
"integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
"dev": true,
"optional": true,
"requires": {
"is-extglob": "^2.1.0"
}
}
}
},
"is-accessor-descriptor": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
"integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
"dev": true,
"optional": true,
"requires": {
"kind-of": "^6.0.0"
}
},
"is-data-descriptor": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
"integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
"dev": true,
"optional": true,
"requires": {
"kind-of": "^6.0.0"
}
},
"is-descriptor": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
"integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
"dev": true,
"optional": true,
"requires": {
"is-accessor-descriptor": "^1.0.0",
"is-data-descriptor": "^1.0.0",
"kind-of": "^6.0.2"
}
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true,
"optional": true
},
"is-glob": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
"dev": true,
"optional": true,
"requires": {
"is-extglob": "^2.1.1"
}
},
"is-number": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
"dev": true,
"optional": true,
"requires": {
"kind-of": "^3.0.2"
},
"dependencies": {
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"optional": true,
"requires": {
"is-buffer": "^1.1.5"
}
}
}
},
"isobject": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
"dev": true,
"optional": true
},
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true,
"optional": true
},
"make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
"dev": true,
"requires": {
"pify": "^4.0.1",
"semver": "^5.6.0"
}
},
"micromatch": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
"integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
"dev": true,
"optional": true,
"requires": {
"arr-diff": "^4.0.0",
"array-unique": "^0.3.2",
"braces": "^2.3.1",
"define-property": "^2.0.2",
"extend-shallow": "^3.0.2",
"extglob": "^2.0.4",
"fragment-cache": "^0.2.1",
"kind-of": "^6.0.2",
"nanomatch": "^1.2.9",
"object.pick": "^1.3.0",
"regex-not": "^1.0.0",
"snapdragon": "^0.8.1",
"to-regex": "^3.0.2"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true,
"optional": true
},
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"optional": true
},
"slash": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
"dev": true
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true
}
}
},
"@babel/code-frame": { "@babel/code-frame": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
@ -15788,9 +16257,9 @@
} }
}, },
"scratch-gui": { "scratch-gui": {
"version": "0.1.0-prerelease.20200330211052", "version": "0.1.0-prerelease.20200325184934",
"resolved": "https://registry.npmjs.org/scratch-gui/-/scratch-gui-0.1.0-prerelease.20200330211052.tgz", "resolved": "https://registry.npmjs.org/scratch-gui/-/scratch-gui-0.1.0-prerelease.20200325184934.tgz",
"integrity": "sha512-ANInG5YteFY2HnCXKBA1weyxSljFIGI2rfIhft2FIXKlyJA28QO8WqCwD008kdiRg2Mj9IXQNpkjWJbP8y5dzQ==", "integrity": "sha512-9iwSyff0s3Y6FSoFVZPAMMkKFumuG8pzdIwkGQlBcADGZ6w7/7HJcXqhAVHwBeeb4kYL2cSkSdcTICEeaIze0g==",
"dev": true "dev": true
}, },
"scratch-l10n": { "scratch-l10n": {
@ -15803,475 +16272,6 @@
"@babel/core": "^7.1.2", "@babel/core": "^7.1.2",
"babel-plugin-react-intl": "^3.0.1", "babel-plugin-react-intl": "^3.0.1",
"transifex": "1.6.6" "transifex": "1.6.6"
},
"dependencies": {
"@babel/cli": {
"version": "7.8.4",
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.8.4.tgz",
"integrity": "sha512-XXLgAm6LBbaNxaGhMAznXXaxtCWfuv6PIDJ9Alsy9JYTOh+j2jJz+L/162kkfU1j/pTSxK1xGmlwI4pdIMkoag==",
"dev": true,
"requires": {
"chokidar": "^2.1.8",
"commander": "^4.0.1",
"convert-source-map": "^1.1.0",
"fs-readdir-recursive": "^1.1.0",
"glob": "^7.0.0",
"lodash": "^4.17.13",
"make-dir": "^2.1.0",
"slash": "^2.0.0",
"source-map": "^0.5.0"
}
},
"anymatch": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
"integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
"dev": true,
"optional": true,
"requires": {
"micromatch": "^3.1.4",
"normalize-path": "^2.1.1"
},
"dependencies": {
"normalize-path": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
"integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
"dev": true,
"optional": true,
"requires": {
"remove-trailing-separator": "^1.0.1"
}
}
}
},
"arr-diff": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
"integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
"dev": true,
"optional": true
},
"array-unique": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
"dev": true,
"optional": true
},
"braces": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
"dev": true,
"optional": true,
"requires": {
"arr-flatten": "^1.1.0",
"array-unique": "^0.3.2",
"extend-shallow": "^2.0.1",
"fill-range": "^4.0.0",
"isobject": "^3.0.1",
"repeat-element": "^1.1.2",
"snapdragon": "^0.8.1",
"snapdragon-node": "^2.0.1",
"split-string": "^3.0.2",
"to-regex": "^3.0.1"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"optional": true,
"requires": {
"is-extendable": "^0.1.0"
}
}
}
},
"chokidar": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
"integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
"dev": true,
"optional": true,
"requires": {
"anymatch": "^2.0.0",
"async-each": "^1.0.1",
"braces": "^2.3.2",
"fsevents": "^1.2.7",
"glob-parent": "^3.1.0",
"inherits": "^2.0.3",
"is-binary-path": "^1.0.0",
"is-glob": "^4.0.0",
"normalize-path": "^3.0.0",
"path-is-absolute": "^1.0.0",
"readdirp": "^2.2.1",
"upath": "^1.1.1"
}
},
"commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"dev": true
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"optional": true,
"requires": {
"ms": "2.0.0"
}
},
"expand-brackets": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
"integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
"dev": true,
"optional": true,
"requires": {
"debug": "^2.3.3",
"define-property": "^0.2.5",
"extend-shallow": "^2.0.1",
"posix-character-classes": "^0.1.0",
"regex-not": "^1.0.0",
"snapdragon": "^0.8.1",
"to-regex": "^3.0.1"
},
"dependencies": {
"define-property": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
"dev": true,
"optional": true,
"requires": {
"is-descriptor": "^0.1.0"
}
},
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"optional": true,
"requires": {
"is-extendable": "^0.1.0"
}
},
"is-accessor-descriptor": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
"integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
"dev": true,
"optional": true,
"requires": {
"kind-of": "^3.0.2"
},
"dependencies": {
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"optional": true,
"requires": {
"is-buffer": "^1.1.5"
}
}
}
},
"is-data-descriptor": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
"integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
"dev": true,
"optional": true,
"requires": {
"kind-of": "^3.0.2"
},
"dependencies": {
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"optional": true,
"requires": {
"is-buffer": "^1.1.5"
}
}
}
},
"is-descriptor": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
"integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
"dev": true,
"optional": true,
"requires": {
"is-accessor-descriptor": "^0.1.6",
"is-data-descriptor": "^0.1.4",
"kind-of": "^5.0.0"
}
},
"kind-of": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
"integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
"dev": true,
"optional": true
}
}
},
"extglob": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
"integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
"dev": true,
"optional": true,
"requires": {
"array-unique": "^0.3.2",
"define-property": "^1.0.0",
"expand-brackets": "^2.1.4",
"extend-shallow": "^2.0.1",
"fragment-cache": "^0.2.1",
"regex-not": "^1.0.0",
"snapdragon": "^0.8.1",
"to-regex": "^3.0.1"
},
"dependencies": {
"define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
"dev": true,
"optional": true,
"requires": {
"is-descriptor": "^1.0.0"
}
},
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"optional": true,
"requires": {
"is-extendable": "^0.1.0"
}
}
}
},
"fill-range": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
"dev": true,
"optional": true,
"requires": {
"extend-shallow": "^2.0.1",
"is-number": "^3.0.0",
"repeat-string": "^1.6.1",
"to-regex-range": "^2.1.0"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"optional": true,
"requires": {
"is-extendable": "^0.1.0"
}
}
}
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"glob-parent": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
"integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
"dev": true,
"optional": true,
"requires": {
"is-glob": "^3.1.0",
"path-dirname": "^1.0.0"
},
"dependencies": {
"is-glob": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
"integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
"dev": true,
"optional": true,
"requires": {
"is-extglob": "^2.1.0"
}
}
}
},
"is-accessor-descriptor": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
"integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
"dev": true,
"optional": true,
"requires": {
"kind-of": "^6.0.0"
}
},
"is-data-descriptor": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
"integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
"dev": true,
"optional": true,
"requires": {
"kind-of": "^6.0.0"
}
},
"is-descriptor": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
"integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
"dev": true,
"optional": true,
"requires": {
"is-accessor-descriptor": "^1.0.0",
"is-data-descriptor": "^1.0.0",
"kind-of": "^6.0.2"
}
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true,
"optional": true
},
"is-glob": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
"dev": true,
"optional": true,
"requires": {
"is-extglob": "^2.1.1"
}
},
"is-number": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
"dev": true,
"optional": true,
"requires": {
"kind-of": "^3.0.2"
},
"dependencies": {
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"optional": true,
"requires": {
"is-buffer": "^1.1.5"
}
}
}
},
"isobject": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
"dev": true,
"optional": true
},
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true,
"optional": true
},
"make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
"dev": true,
"requires": {
"pify": "^4.0.1",
"semver": "^5.6.0"
}
},
"micromatch": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
"integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
"dev": true,
"optional": true,
"requires": {
"arr-diff": "^4.0.0",
"array-unique": "^0.3.2",
"braces": "^2.3.1",
"define-property": "^2.0.2",
"extend-shallow": "^3.0.2",
"extglob": "^2.0.4",
"fragment-cache": "^0.2.1",
"kind-of": "^6.0.2",
"nanomatch": "^1.2.9",
"object.pick": "^1.3.0",
"regex-not": "^1.0.0",
"snapdragon": "^0.8.1",
"to-regex": "^3.0.2"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true,
"optional": true
},
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"optional": true
},
"slash": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
"dev": true
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true
}
} }
}, },
"scratch-parser": { "scratch-parser": {

View file

@ -128,8 +128,8 @@
"redux-mock-store": "^1.2.3", "redux-mock-store": "^1.2.3",
"redux-thunk": "2.0.1", "redux-thunk": "2.0.1",
"sass-loader": "6.0.6", "sass-loader": "6.0.6",
"scratch-gui": "0.1.0-prerelease.20200330211052", "scratch-gui": "0.1.0-prerelease.20200325184934",
"scratch-l10n": "^3.8.20200325112845", "scratch-l10n": "latest",
"selenium-webdriver": "3.6.0", "selenium-webdriver": "3.6.0",
"slick-carousel": "1.6.0", "slick-carousel": "1.6.0",
"source-map-support": "0.3.2", "source-map-support": "0.3.2",

View file

@ -0,0 +1,76 @@
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
class Captcha extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'setCaptchaRef',
'onCaptchaLoad',
'executeCaptcha'
]);
}
componentDidMount () {
if (window.grecaptcha) {
this.onCaptchaLoad();
} else {
// If grecaptcha doesn't exist on window, we havent loaded the captcha js yet. Load it.
// ReCaptcha calls a callback when the grecatpcha object is usable. That callback
// needs to be global so set it on the window.
window.grecaptchaOnLoad = this.onCaptchaLoad;
// Load Google ReCaptcha script.
const script = document.createElement('script');
script.async = true;
script.onerror = this.props.onCaptchaError;
script.src = `https://www.recaptcha.net/recaptcha/api.js?onload=grecaptchaOnLoad&render=explicit&hl=${window._locale}`;
document.body.appendChild(script);
}
}
componentWillUnmount () {
window.grecaptchaOnLoad = null;
}
onCaptchaLoad () {
// Let the owner of this component do some work
// when captcha is done loading (e.g. enabling a button)
this.props.onCaptchaLoad();
this.grecaptcha = window.grecaptcha;
if (!this.grecaptcha) {
// According to the reCaptcha documentation, this callback shouldn't get
// called unless window.grecaptcha exists. This is just here to be extra defensive.
this.props.onCaptchaError();
return;
}
this.widgetId = this.grecaptcha.render(this.captchaRef,
{
callback: this.props.onCaptchaSolved,
sitekey: process.env.RECAPTCHA_SITE_KEY
},
true);
}
setCaptchaRef (ref) {
this.captchaRef = ref;
}
executeCaptcha () {
this.grecaptcha.execute(this.widgetId);
}
render () {
return (
<div
className="g-recaptcha"
data-badge="bottomright"
data-sitekey={process.env.RECAPTCHA_SITE_KEY}
data-size="invisible"
ref={this.setCaptchaRef}
/>
);
}
}
Captcha.propTypes = {
onCaptchaError: PropTypes.func.isRequired,
onCaptchaLoad: PropTypes.func.isRequired,
onCaptchaSolved: PropTypes.func.isRequired
};
module.exports = Captcha;

View file

@ -1,149 +0,0 @@
const React = require('react');
const bindAll = require('lodash.bindall');
const Modal = require('../modal/base/modal.jsx');
const ModalTitle = require('../modal/base/modal-title.jsx');
require('./cats.scss');
const catImages = [
'/images/cats/IMG_9775.jpg',
'/images/cats/IMG_9766.jpg',
'/images/cats/IMG_9587.jpg',
'/images/cats/IMG_6558.jpg',
'/images/cats/IMG_6521.jpg',
'/images/cats/IMG_6020.jpg',
'/images/cats/IMG_5880.jpg',
'/images/cats/IMG_3218.jpg',
'/images/cats/IMG_2776.jpg',
'/images/cats/IMG_2775.jpg',
'/images/cats/IMG_2681.jpg',
'/images/cats/IMG_1092.jpg',
'/images/cats/IMG_0684.jpg',
'/images/cats/IMG_0698.jpg',
'/images/cats/IMG_0504.jpg',
'/images/cats/IMG_0288.jpg',
'/images/cats/IMG_0122.jpg',
'/images/cats/IMG_2507.jpg',
'/images/cats/IMG_1977.jpg',
'/images/cats/IMG_1696.jpg',
'/images/cats/IMG_1463.jpg',
'/images/cats/IMG_1157.jpg',
'/images/cats/IMG_0681.jpg',
'/images/cats/IMG_0135.jpg',
'/images/cats/IMG_0071.jpg',
'/images/cats/1.jpg',
'/images/cats/2.jpg',
'/images/cats/3.jpg',
'/images/cats/4.jpg',
'/images/cats/Cat1.jpg',
'/images/cats/Cat2.jpg',
'/images/cats/Cat3.jpg',
'/images/cats/Cat4.jpg',
'/images/cats/Cat5.jpg',
'/images/cats/Cat6.png',
'/images/cats/Cat7.jpg',
'/images/cats/Cat8.jpg',
'/images/cats/Cat9.jpg',
'/images/cats/Cat10.png',
'/images/cats/Cat11.png',
'/images/cats/Cat12.png',
'/images/cats/IMG_2167.jpg',
'/images/cats/IMG_4316.jpg',
'/images/cats/IMG_5396.jpg'
];
/* eslint-disable max-len */
const mysteryFacts = [
'How small can they make a t-shirt?',
'Why do hotdogs come in packages of 8?',
'Who let the dogs out?',
'Why are cats so cute?',
'Where is the other sock?',
'Why is the sky blue?',
'Why does everyday end in y?',
'How many licks does it take to get to the center of a lollipop? ',
'How many bites does it take to get to the center of a corndog?',
'How many hours can a cat sleep in one day?',
'Am I hungry?',
'What should I eat?',
'Who will make me a sandwich?',
'Where are my glasses?',
'Why did I walk into this room?',
'Where is the bathroom?',
'Where is the mop?',
'Why are ants so strong?',
'Why do I wake up before my alarm goes off?',
'Where do almonds come from?',
'When is the sky blue?',
'Wherefore art thou Romeo?',
'What was the Scratch Cat like as a kitten?',
'What is the plural of Moose?',
'Whose chair is that?',
'Who paid for that floor?',
'What is my cat\'s favorite color?',
'What is that cat\'s favorite color?',
'Does the "close door" button on an elevator really work?'
];
/* eslint-enable max-len */
class Cats extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleCatsClick',
'handleClose',
'pickRandomFact'
]);
this.state = {
open: false
};
}
handleCatsClick () {
this.setState({open: true});
}
handleClose () {
this.setState({open: false});
}
pickRandomFact () {
const randomNumber = Math.floor(Math.random() * (mysteryFacts.length - 1));
const catFact = mysteryFacts[randomNumber];
return catFact;
}
pickRandomCatImage () {
const randomNumber = Math.floor(Math.random() * (catImages.length - 1));
const catImage = catImages[randomNumber];
return catImage;
}
render () {
return (<React.Fragment>
<div onClick={this.handleCatsClick}>
<a> {'Mystery'} </a>
</div>
<Modal
useStandardSizes
className="mod-cats"
isOpen={this.state.open}
onRequestClose={this.handleClose}
>
<div className="cats-modal-header modal-header">
<ModalTitle title={'Hmmm... 🤔'} />
</div>
<div className="cats-modal-content modal-content">
<p> {this.pickRandomFact()} </p>
<img src={this.pickRandomCatImage()} />
</div>
</Modal>
</React.Fragment>
);
}
}
module.exports = Cats;

View file

@ -1,34 +0,0 @@
@import "../../colors";
@import "../../frameless";
.mod-cats {
min-height: 15rem;
max-height: calc(100% - 5rem);
overflow: hidden;
}
.cats-modal-header {
box-shadow: inset 0 -1px 0 0 $ui-blue-dark;
background-color: $ui-blue;
}
.cats-modal-content {
margin: 0 auto;
box-shadow: none;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 50px;
}
.cats-modal-content img {
width: 400px;
@media #{$small}, #{$small-height} {
width: 100%;
}
}

View file

@ -6,7 +6,6 @@ const React = require('react');
const FooterBox = require('../container/footer.jsx'); const FooterBox = require('../container/footer.jsx');
const LanguageChooser = require('../../languagechooser/languagechooser.jsx'); const LanguageChooser = require('../../languagechooser/languagechooser.jsx');
const Cats = require('../../cats/cats.jsx');
const frameless = require('../../../lib/frameless'); const frameless = require('../../../lib/frameless');
@ -32,9 +31,6 @@ const Footer = props => (
<FormattedMessage id="general.contactUs" /> <FormattedMessage id="general.contactUs" />
</a> </a>
</dd> </dd>
<dd>
<Cats />
</dd>
</dl> </dl>
<dl> <dl>
<dd> <dd>
@ -96,9 +92,6 @@ const Footer = props => (
<FormattedMessage id="general.press" /> <FormattedMessage id="general.press" />
</a> </a>
</dd> </dd>
<dd>
<Cats />
</dd>
</dl> </dl>
<dl> <dl>
<dt> <dt>

View file

@ -11,7 +11,7 @@ const JoinFlowStep = require('./join-flow-step.jsx');
const FormikInput = require('../../components/formik-forms/formik-input.jsx'); const FormikInput = require('../../components/formik-forms/formik-input.jsx');
const FormikCheckbox = require('../../components/formik-forms/formik-checkbox.jsx'); const FormikCheckbox = require('../../components/formik-forms/formik-checkbox.jsx');
const InfoButton = require('../info-button/info-button.jsx'); const InfoButton = require('../info-button/info-button.jsx');
const Captcha = require('../../components/captcha/captcha.jsx');
require('./join-flow-steps.scss'); require('./join-flow-steps.scss');
class EmailStep extends React.Component { class EmailStep extends React.Component {
@ -24,8 +24,8 @@ class EmailStep extends React.Component {
'validateEmailRemotelyWithCache', 'validateEmailRemotelyWithCache',
'validateForm', 'validateForm',
'setCaptchaRef', 'setCaptchaRef',
'captchaSolved', 'handleCaptchaSolved',
'onCaptchaLoad' 'handleCaptchaLoad'
]); ]);
this.state = { this.state = {
captchaIsLoading: true captchaIsLoading: true
@ -40,43 +40,12 @@ class EmailStep extends React.Component {
} }
// automatically start with focus on username field // automatically start with focus on username field
if (this.emailInput) this.emailInput.focus(); if (this.emailInput) this.emailInput.focus();
if (window.grecaptcha) {
this.onCaptchaLoad();
} else {
// If grecaptcha doesn't exist on window, we havent loaded the captcha js yet. Load it.
// ReCaptcha calls a callback when the grecatpcha object is usable. That callback
// needs to be global so set it on the window.
window.grecaptchaOnLoad = this.onCaptchaLoad;
// Load Google ReCaptcha script.
const script = document.createElement('script');
script.async = true;
script.onerror = this.props.onCaptchaError;
script.src = `https://www.recaptcha.net/recaptcha/api.js?onload=grecaptchaOnLoad&render=explicit&hl=${window._locale}`;
document.body.appendChild(script);
}
}
componentWillUnmount () {
window.grecaptchaOnLoad = null;
} }
handleSetEmailRef (emailInputRef) { handleSetEmailRef (emailInputRef) {
this.emailInput = emailInputRef; this.emailInput = emailInputRef;
} }
onCaptchaLoad () { handleCaptchaLoad () {
this.setState({captchaIsLoading: false}); this.setState({captchaIsLoading: false});
this.grecaptcha = window.grecaptcha;
if (!this.grecaptcha) {
// According to the reCaptcha documentation, this callback shouldn't get
// called unless window.grecaptcha exists. This is just here to be extra defensive.
this.props.onCaptchaError();
return;
}
this.widgetId = this.grecaptcha.render(this.captchaRef,
{
callback: this.captchaSolved,
sitekey: process.env.RECAPTCHA_SITE_KEY
},
true);
} }
// simple function to memoize remote requests for usernames // simple function to memoize remote requests for usernames
validateEmailRemotelyWithCache (email) { validateEmailRemotelyWithCache (email) {
@ -116,9 +85,9 @@ class EmailStep extends React.Component {
// Change set submitting to false so that if the user clicks out of // Change set submitting to false so that if the user clicks out of
// the captcha, the button is clickable again (instead of a disabled button with a spinner). // the captcha, the button is clickable again (instead of a disabled button with a spinner).
this.formikBag.setSubmitting(false); this.formikBag.setSubmitting(false);
this.grecaptcha.execute(this.widgetId); this.captchaRef.executeCaptcha();
} }
captchaSolved (token) { handleCaptchaSolved (token) {
// Now thatcaptcha is done, we can tell Formik we're submitting. // Now thatcaptcha is done, we can tell Formik we're submitting.
this.formikBag.setSubmitting(true); this.formikBag.setSubmitting(true);
this.formData['g-recaptcha-response'] = token; this.formData['g-recaptcha-response'] = token;
@ -224,12 +193,11 @@ class EmailStep extends React.Component {
name="subscribe" name="subscribe"
/> />
</div> </div>
<div <Captcha
className="g-recaptcha"
data-badge="bottomright"
data-sitekey={process.env.RECAPTCHA_SITE_KEY}
data-size="invisible"
ref={this.setCaptchaRef} ref={this.setCaptchaRef}
onCaptchaError={this.props.onCaptchaError}
onCaptchaLoad={this.handleCaptchaLoad}
onCaptchaSolved={this.handleCaptchaSolved}
/> />
</JoinFlowStep> </JoinFlowStep>
); );

View file

@ -12,6 +12,7 @@ const intl = require('../../lib/intl.jsx');
const Avatar = require('../../components/avatar/avatar.jsx'); const Avatar = require('../../components/avatar/avatar.jsx');
const Button = require('../../components/forms/button.jsx'); const Button = require('../../components/forms/button.jsx');
const Captcha = require('../../components/captcha/captcha.jsx');
const Card = require('../../components/card/card.jsx'); const Card = require('../../components/card/card.jsx');
const CharCount = require('../../components/forms/charcount.jsx'); const CharCount = require('../../components/forms/charcount.jsx');
const Checkbox = require('../../components/forms/checkbox.jsx'); const Checkbox = require('../../components/forms/checkbox.jsx');
@ -1198,12 +1199,36 @@ class EmailStep extends React.Component {
constructor (props) { constructor (props) {
super(props); super(props);
bindAll(this, [ bindAll(this, [
'handleValidSubmit' 'handleCaptchaError',
'handleCaptchaLoad',
'handleCaptchaSolved',
'handleValidSubmit',
'setCaptchaRef'
]); ]);
this.state = { this.state = {
waiting: false waiting: false
}; };
} }
handleCaptchaLoad () {
this.setState({
waiting: true
});
}
handleCaptchaSolved (token) {
this.setState({
waiting: false
});
this.formData['g-recaptcha-response'] = token;
this.setState({'g-recaptcha-response': token});
this.props.onNextStep(this.formData);
}
handleCaptchaError () {
this.props.setRegistrationError(
this.props.intl.formatMessage({id: 'registration.errorCaptcha'}));
}
setCaptchaRef (ref) {
this.captchaRef = ref;
}
handleValidSubmit (formData, reset, invalidate) { handleValidSubmit (formData, reset, invalidate) {
this.setState({waiting: true}); this.setState({waiting: true});
api({ api({
@ -1219,7 +1244,8 @@ class EmailStep extends React.Component {
res = res[0]; res = res[0];
switch (res.msg) { switch (res.msg) {
case 'valid email': case 'valid email':
return this.props.onNextStep(formData); this.formData = formData;
return this.captchaRef.executeCaptcha();
default: default:
return invalidate({'user.email': res.msg}); return invalidate({'user.email': res.msg});
} }
@ -1283,6 +1309,12 @@ class EmailStep extends React.Component {
active={this.props.activeStep} active={this.props.activeStep}
steps={this.props.totalSteps - 1} steps={this.props.totalSteps - 1}
/> />
<Captcha
ref={this.setCaptchaRef}
onCaptchaError={this.handleCaptchaError}
onCaptchaLoad={this.handleCaptchaLoad}
onCaptchaSolved={this.handleCaptchaSolved}
/>
</Slide> </Slide>
); );
} }
@ -1292,6 +1324,7 @@ EmailStep.propTypes = {
activeStep: PropTypes.number, activeStep: PropTypes.number,
intl: intlShape, intl: intlShape,
onNextStep: PropTypes.func, onNextStep: PropTypes.func,
setRegistrationError: PropTypes.func,
totalSteps: PropTypes.number, totalSteps: PropTypes.number,
waiting: PropTypes.bool waiting: PropTypes.bool
}; };

View file

@ -79,5 +79,5 @@
} }
</script> </script>
<% } %> <% } %>
<div style="color: #fff">;</div></body> </body>
</html> </html>

View file

@ -23,7 +23,8 @@ class TeacherRegistration extends React.Component {
super(props); super(props);
bindAll(this, [ bindAll(this, [
'handleAdvanceStep', 'handleAdvanceStep',
'handleRegister' 'handleRegister',
'setRegistrationError'
]); ]);
this.state = { this.state = {
formData: {}, formData: {},
@ -32,6 +33,9 @@ class TeacherRegistration extends React.Component {
waiting: false waiting: false
}; };
} }
setRegistrationError (err) {
this.setState({registrationError: err});
}
handleAdvanceStep (formData) { handleAdvanceStep (formData) {
formData = formData || {}; formData = formData || {};
this.setState({ this.setState({
@ -47,34 +51,35 @@ class TeacherRegistration extends React.Component {
method: 'post', method: 'post',
useCsrf: true, useCsrf: true,
formData: { formData: {
username: this.state.formData.user.username, 'username': this.state.formData.user.username,
email: formData.user.email, 'email': formData.user.email,
password: this.state.formData.user.password, 'g-recaptcha-response': formData['g-recaptcha-response'],
birth_month: this.state.formData.user.birth.month, 'password': this.state.formData.user.password,
birth_year: this.state.formData.user.birth.year, 'birth_month': this.state.formData.user.birth.month,
gender: ( 'birth_year': this.state.formData.user.birth.year,
'gender': (
this.state.formData.user.gender === 'other' ? this.state.formData.user.gender === 'other' ?
this.state.formData.user.genderOther : this.state.formData.user.genderOther :
this.state.formData.user.gender this.state.formData.user.gender
), ),
country: this.state.formData.user.country, 'country': this.state.formData.user.country,
subscribe: formData.subscribe, 'subscribe': formData.subscribe,
is_robot: this.state.formData.user.isRobot, 'is_robot': this.state.formData.user.isRobot,
first_name: this.state.formData.user.name.first, 'first_name': this.state.formData.user.name.first,
last_name: this.state.formData.user.name.last, 'last_name': this.state.formData.user.name.last,
phone_number: this.state.formData.phone.national_number, 'phone_number': this.state.formData.phone.national_number,
organization_name: this.state.formData.organization.name, 'organization_name': this.state.formData.organization.name,
organization_title: this.state.formData.organization.title, 'organization_title': this.state.formData.organization.title,
organization_type: this.state.formData.organization.type, 'organization_type': this.state.formData.organization.type,
organization_other: this.state.formData.organization.other, 'organization_other': this.state.formData.organization.other,
organization_url: this.state.formData.organization.url, 'organization_url': this.state.formData.organization.url,
address_country: this.state.formData.address.country, 'address_country': this.state.formData.address.country,
address_line1: this.state.formData.address.line1, 'address_line1': this.state.formData.address.line1,
address_line2: this.state.formData.address.line2, 'address_line2': this.state.formData.address.line2,
address_city: this.state.formData.address.city, 'address_city': this.state.formData.address.city,
address_state: this.state.formData.address.state, 'address_state': this.state.formData.address.state,
address_zip: this.state.formData.address.zip, 'address_zip': this.state.formData.address.zip,
how_use_scratch: this.state.formData.useScratch 'how_use_scratch': this.state.formData.useScratch
} }
}, (err, body, res) => { }, (err, body, res) => {
this.setState({waiting: false}); this.setState({waiting: false});
@ -133,6 +138,7 @@ class TeacherRegistration extends React.Component {
onNextStep={this.handleAdvanceStep} onNextStep={this.handleAdvanceStep}
/> />
<Steps.EmailStep <Steps.EmailStep
setRegistrationError={this.setRegistrationError}
waiting={this.state.waiting} waiting={this.state.waiting}
onNextStep={this.handleRegister} onNextStep={this.handleRegister}
/> />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View file

@ -0,0 +1,57 @@
const React = require('react');
const enzyme = require('enzyme');
const Captcha = require('../../../src/components/captcha/captcha.jsx');
describe('Captcha test', () => {
global.grecaptcha = {
execute: jest.fn(),
render: jest.fn()
};
test('Captcha load calls props captchaOnLoad', () => {
const props = {
onCaptchaLoad: jest.fn()
};
const wrapper = enzyme.shallow(<Captcha
{...props}
/>);
wrapper.instance().onCaptchaLoad();
expect(global.grecaptcha.render).toHaveBeenCalled();
expect(props.onCaptchaLoad).toHaveBeenCalled();
});
test('Captcha execute calls grecatpcha execute', () => {
const props = {
onCaptchaLoad: jest.fn()
};
const wrapper = enzyme.shallow(<Captcha
{...props}
/>);
wrapper.instance().executeCaptcha();
expect(global.grecaptcha.execute).toHaveBeenCalled();
});
test('Captcha load calls props captchaOnLoad', () => {
const props = {
onCaptchaLoad: jest.fn()
};
const wrapper = enzyme.shallow(<Captcha
{...props}
/>);
wrapper.instance().onCaptchaLoad();
expect(global.grecaptcha.render).toHaveBeenCalled();
expect(props.onCaptchaLoad).toHaveBeenCalled();
});
test('Captcha renders the div google wants', () => {
const props = {
onCaptchaLoad: jest.fn()
};
const wrapper = enzyme.mount(<Captcha
{...props}
/>);
expect(wrapper.find('div.g-recaptcha')).toHaveLength(1);
});
});

View file

@ -115,9 +115,9 @@ describe('EmailStep test', () => {
const formikBag = { const formikBag = {
setSubmitting: jest.fn() setSubmitting: jest.fn()
}; };
global.grecaptcha = {
execute: jest.fn(), const captchaRef = {
render: jest.fn() executeCaptcha: jest.fn()
}; };
const formData = {item1: 'thing', item2: 'otherthing'}; const formData = {item1: 'thing', item2: 'otherthing'};
const intlWrapper = shallowWithIntl( const intlWrapper = shallowWithIntl(
@ -125,13 +125,12 @@ describe('EmailStep test', () => {
{...defaultProps()} {...defaultProps()}
/>); />);
const emailStepWrapper = intlWrapper.dive(); const emailStepWrapper = intlWrapper.dive();
emailStepWrapper.instance().onCaptchaLoad(); // to setup catpcha state emailStepWrapper.instance().setCaptchaRef(captchaRef);
emailStepWrapper.instance().handleValidSubmit(formData, formikBag); emailStepWrapper.instance().handleValidSubmit(formData, formikBag);
expect(formikBag.setSubmitting).toHaveBeenCalledWith(false); expect(formikBag.setSubmitting).toHaveBeenCalledWith(false);
expect(global.grecaptcha.execute).toHaveBeenCalled(); expect(captchaRef.executeCaptcha).toHaveBeenCalled();
}); });
test('captchaSolved sets token and goes to next step', () => { test('captchaSolved sets token and goes to next step', () => {
@ -141,9 +140,8 @@ describe('EmailStep test', () => {
const formikBag = { const formikBag = {
setSubmitting: jest.fn() setSubmitting: jest.fn()
}; };
global.grecaptcha = { const captchaRef = {
execute: jest.fn(), executeCaptcha: jest.fn()
render: jest.fn()
}; };
const formData = {item1: 'thing', item2: 'otherthing'}; const formData = {item1: 'thing', item2: 'otherthing'};
const intlWrapper = shallowWithIntl( const intlWrapper = shallowWithIntl(
@ -154,11 +152,11 @@ describe('EmailStep test', () => {
const emailStepWrapper = intlWrapper.dive(); const emailStepWrapper = intlWrapper.dive();
// Call these to setup captcha. // Call these to setup captcha.
emailStepWrapper.instance().onCaptchaLoad(); // to setup catpcha state emailStepWrapper.instance().setCaptchaRef(captchaRef); // to setup catpcha state
emailStepWrapper.instance().handleValidSubmit(formData, formikBag); emailStepWrapper.instance().handleValidSubmit(formData, formikBag);
const captchaToken = 'abcd'; const captchaToken = 'abcd';
emailStepWrapper.instance().captchaSolved(captchaToken); emailStepWrapper.instance().handleCaptchaSolved(captchaToken);
// Make sure captchaSolved calls onNextStep with formData that has // Make sure captchaSolved calls onNextStep with formData that has
// a captcha token and left everything else in the object in place. // a captcha token and left everything else in the object in place.
expect(props.onNextStep).toHaveBeenCalledWith( expect(props.onNextStep).toHaveBeenCalledWith(
@ -170,29 +168,13 @@ describe('EmailStep test', () => {
expect(formikBag.setSubmitting).toHaveBeenCalledWith(true); expect(formikBag.setSubmitting).toHaveBeenCalledWith(true);
}); });
test('Captcha load error calls error function', () => {
const props = {
onCaptchaError: jest.fn()
};
// Set this to null to force an error.
global.grecaptcha = null;
const intlWrapper = shallowWithIntl(
<EmailStep
{...defaultProps()}
{...props}
/>
);
const emailStepWrapper = intlWrapper.dive();
emailStepWrapper.instance().onCaptchaLoad();
expect(props.onCaptchaError).toHaveBeenCalled();
});
test('Component logs analytics', () => { test('Component logs analytics', () => {
const sendAnalyticsFn = jest.fn(); const sendAnalyticsFn = jest.fn();
const onCaptchaError = jest.fn();
mountWithIntl( mountWithIntl(
<EmailStep <EmailStep
sendAnalytics={sendAnalyticsFn} sendAnalytics={sendAnalyticsFn}
onCaptchaError={onCaptchaError}
/>); />);
expect(sendAnalyticsFn).toHaveBeenCalledWith('join-email'); expect(sendAnalyticsFn).toHaveBeenCalledWith('join-email');
}); });