mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-23 15:47:53 -05:00
Merge pull request #3258 from LLK/release/2019-08-15
[Master] Release 2019-08-15
This commit is contained in:
commit
6727cb133c
32 changed files with 791 additions and 105 deletions
91
package-lock.json
generated
91
package-lock.json
generated
|
@ -4035,6 +4035,12 @@
|
||||||
"minimalistic-crypto-utils": "^1.0.0"
|
"minimalistic-crypto-utils": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"email-validator": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/email-validator/-/email-validator-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"emoji-regex": {
|
"emoji-regex": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
|
||||||
|
@ -5474,7 +5480,8 @@
|
||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"aproba": {
|
"aproba": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
|
@ -5495,12 +5502,14 @@
|
||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
|
@ -5515,17 +5524,20 @@
|
||||||
"code-point-at": {
|
"code-point-at": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
|
@ -5642,7 +5654,8 @@
|
||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
|
@ -5654,6 +5667,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"number-is-nan": "^1.0.0"
|
"number-is-nan": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
@ -5668,6 +5682,7 @@
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
|
@ -5675,12 +5690,14 @@
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"minipass": {
|
"minipass": {
|
||||||
"version": "2.3.5",
|
"version": "2.3.5",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "^5.1.2",
|
"safe-buffer": "^5.1.2",
|
||||||
"yallist": "^3.0.0"
|
"yallist": "^3.0.0"
|
||||||
|
@ -5699,6 +5716,7 @@
|
||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "0.0.8"
|
"minimist": "0.0.8"
|
||||||
}
|
}
|
||||||
|
@ -5779,7 +5797,8 @@
|
||||||
"number-is-nan": {
|
"number-is-nan": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"object-assign": {
|
"object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
|
@ -5791,6 +5810,7 @@
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
|
@ -5876,7 +5896,8 @@
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"safer-buffer": {
|
"safer-buffer": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
|
@ -5912,6 +5933,7 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"code-point-at": "^1.0.0",
|
"code-point-at": "^1.0.0",
|
||||||
"is-fullwidth-code-point": "^1.0.0",
|
"is-fullwidth-code-point": "^1.0.0",
|
||||||
|
@ -5931,6 +5953,7 @@
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "^2.0.0"
|
"ansi-regex": "^2.0.0"
|
||||||
}
|
}
|
||||||
|
@ -5974,12 +5997,14 @@
|
||||||
"wrappy": {
|
"wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"yallist": {
|
"yallist": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -7501,12 +7526,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||||
},
|
},
|
||||||
"isemail": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/isemail/-/isemail-1.2.0.tgz",
|
|
||||||
"integrity": "sha1-vgPfjMPineTSxd9lASY/H6RZXpo=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"isexe": {
|
"isexe": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
|
@ -9206,6 +9225,14 @@
|
||||||
"isemail": "1.x.x",
|
"isemail": "1.x.x",
|
||||||
"moment": "2.x.x",
|
"moment": "2.x.x",
|
||||||
"topo": "1.x.x"
|
"topo": "1.x.x"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"isemail": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/isemail/-/isemail-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-vgPfjMPineTSxd9lASY/H6RZXpo=",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jquery": {
|
"jquery": {
|
||||||
|
@ -14929,15 +14956,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scratch-gui": {
|
"scratch-gui": {
|
||||||
"version": "0.1.0-prerelease.20190808151251",
|
"version": "0.1.0-prerelease.20190814215530",
|
||||||
"resolved": "https://registry.npmjs.org/scratch-gui/-/scratch-gui-0.1.0-prerelease.20190808151251.tgz",
|
"resolved": "https://registry.npmjs.org/scratch-gui/-/scratch-gui-0.1.0-prerelease.20190814215530.tgz",
|
||||||
"integrity": "sha512-KBYxva8dWl/XrWTPePveV8oq2RUkhyLoDFQnz2pJSBJOKvkY1SPVCABpc4ocLJ9nOBAtsWOCSJVFBAu24aZD6A==",
|
"integrity": "sha512-6xCCphwTYaBXjlQ7CJjmAnrQOoTLke/da2JAdrSWmGM0WxE+kFAcNpr2DBi7wzPIPTbNuy1nw+ECLb3Bst9LEQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"scratch-l10n": {
|
"scratch-l10n": {
|
||||||
"version": "3.5.20190807144510",
|
"version": "3.5.20190813223429",
|
||||||
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.5.20190807144510.tgz",
|
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.5.20190813223429.tgz",
|
||||||
"integrity": "sha512-rq3G4NZvlFvb0bQFwsqK+anb7S74OSutxqabmaP5R4NRbmkpz0PvbT1zMGKEmEScY+8h5VLkNO6im9RoDsSm7g==",
|
"integrity": "sha512-rSxUSwv0RgZTXUknAWuc7BFZWewiNhrgyPUMos/qAw4GgVMdY1ZRSIHBEIItpCXXYLOzw4ObcNafIim6Taq9NA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/cli": "^7.1.2",
|
"@babel/cli": "^7.1.2",
|
||||||
|
@ -14998,13 +15025,15 @@
|
||||||
"version": "0.3.2",
|
"version": "0.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
|
||||||
"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
|
"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"braces": {
|
"braces": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
|
||||||
"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
|
"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"arr-flatten": "^1.1.0",
|
"arr-flatten": "^1.1.0",
|
||||||
"array-unique": "^0.3.2",
|
"array-unique": "^0.3.2",
|
||||||
|
@ -15023,6 +15052,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-extendable": "^0.1.0"
|
"is-extendable": "^0.1.0"
|
||||||
}
|
}
|
||||||
|
@ -15205,6 +15235,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
|
||||||
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
|
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"extend-shallow": "^2.0.1",
|
"extend-shallow": "^2.0.1",
|
||||||
"is-number": "^3.0.0",
|
"is-number": "^3.0.0",
|
||||||
|
@ -15217,6 +15248,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-extendable": "^0.1.0"
|
"is-extendable": "^0.1.0"
|
||||||
}
|
}
|
||||||
|
@ -15296,7 +15328,8 @@
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
|
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"is-glob": {
|
"is-glob": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
|
@ -15313,6 +15346,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
|
||||||
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
|
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"kind-of": "^3.0.2"
|
"kind-of": "^3.0.2"
|
||||||
},
|
},
|
||||||
|
@ -15322,6 +15356,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
||||||
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
|
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-buffer": "^1.1.5"
|
"is-buffer": "^1.1.5"
|
||||||
}
|
}
|
||||||
|
@ -15332,13 +15367,15 @@
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
|
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"kind-of": {
|
"kind-of": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
|
||||||
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
|
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"micromatch": {
|
"micromatch": {
|
||||||
"version": "3.1.10",
|
"version": "3.1.10",
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
"test:unit:jest": "jest ./test/unit/ && jest ./test/localization/*.test.js",
|
"test:unit:jest": "jest ./test/unit/ && jest ./test/localization/*.test.js",
|
||||||
"test:unit:tap": "tap ./test/{unit-legacy,localization-legacy}/*.js --no-coverage -R classic",
|
"test:unit:tap": "tap ./test/{unit-legacy,localization-legacy}/*.js --no-coverage -R classic",
|
||||||
"test:coverage": "tap ./test/{unit-legacy,localization-legacy}/*.js --coverage --coverage-report=lcov",
|
"test:coverage": "tap ./test/{unit-legacy,localization-legacy}/*.js --coverage --coverage-report=lcov",
|
||||||
"build": "npm run clean && npm run translate && webpack --bail",
|
"build": "npm run clean && npm run translate && NODE_OPTIONS=--max_old_space_size=8000 webpack --bail",
|
||||||
"clean": "rm -rf ./build && rm -rf ./intl && mkdir -p build && mkdir -p intl",
|
"clean": "rm -rf ./build && rm -rf ./intl && mkdir -p build && mkdir -p intl",
|
||||||
"deploy": "npm run deploy:s3 && npm run deploy:fastly",
|
"deploy": "npm run deploy:s3 && npm run deploy:fastly",
|
||||||
"deploy:fastly": "node ./bin/configure-fastly.js",
|
"deploy:fastly": "node ./bin/configure-fastly.js",
|
||||||
|
@ -72,6 +72,7 @@
|
||||||
"copy-webpack-plugin": "0.2.0",
|
"copy-webpack-plugin": "0.2.0",
|
||||||
"create-react-class": "15.6.2",
|
"create-react-class": "15.6.2",
|
||||||
"css-loader": "0.23.1",
|
"css-loader": "0.23.1",
|
||||||
|
"email-validator": "2.0.4",
|
||||||
"enzyme": "3.10.0",
|
"enzyme": "3.10.0",
|
||||||
"enzyme-adapter-react-16": "1.14.0",
|
"enzyme-adapter-react-16": "1.14.0",
|
||||||
"eslint": "5.16.0",
|
"eslint": "5.16.0",
|
||||||
|
@ -123,7 +124,7 @@
|
||||||
"redux": "3.5.2",
|
"redux": "3.5.2",
|
||||||
"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.20190808151251",
|
"scratch-gui": "0.1.0-prerelease.20190814215530",
|
||||||
"scratch-l10n": "latest",
|
"scratch-l10n": "latest",
|
||||||
"selenium-webdriver": "3.6.0",
|
"selenium-webdriver": "3.6.0",
|
||||||
"slick-carousel": "1.6.0",
|
"slick-carousel": "1.6.0",
|
||||||
|
|
83
src/components/formik-forms/formik-checkbox.jsx
Normal file
83
src/components/formik-forms/formik-checkbox.jsx
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
const classNames = require('classnames');
|
||||||
|
const PropTypes = require('prop-types');
|
||||||
|
const React = require('react');
|
||||||
|
import {Field} from 'formik';
|
||||||
|
|
||||||
|
require('./formik-checkbox.scss');
|
||||||
|
require('./formik-forms.scss');
|
||||||
|
require('../forms/row.scss');
|
||||||
|
|
||||||
|
const FormikCheckboxSubComponent = ({
|
||||||
|
className,
|
||||||
|
field,
|
||||||
|
id,
|
||||||
|
label,
|
||||||
|
...props
|
||||||
|
}) => (
|
||||||
|
<div className="checkbox">
|
||||||
|
<input
|
||||||
|
checked={field.value}
|
||||||
|
className={classNames(
|
||||||
|
'formik-checkbox',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
id={id}
|
||||||
|
name={field.name}
|
||||||
|
type="checkbox"
|
||||||
|
value={field.value}
|
||||||
|
onBlur={field.onBlur} /* eslint-disable-line react/jsx-handler-names */
|
||||||
|
onChange={field.onChange} /* eslint-disable-line react/jsx-handler-names */
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
{label && (
|
||||||
|
<label
|
||||||
|
className={classNames(
|
||||||
|
'formik-label',
|
||||||
|
'formik-checkbox-label'
|
||||||
|
)}
|
||||||
|
htmlFor={id}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
FormikCheckboxSubComponent.propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
field: PropTypes.shape({
|
||||||
|
name: PropTypes.string,
|
||||||
|
onBlur: PropTypes.function,
|
||||||
|
onChange: PropTypes.function,
|
||||||
|
value: PropTypes.bool
|
||||||
|
}),
|
||||||
|
id: PropTypes.string,
|
||||||
|
label: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const FormikCheckbox = ({
|
||||||
|
className,
|
||||||
|
id,
|
||||||
|
label,
|
||||||
|
name,
|
||||||
|
...props
|
||||||
|
}) => (
|
||||||
|
<Field
|
||||||
|
className={className}
|
||||||
|
component={FormikCheckboxSubComponent}
|
||||||
|
id={id}
|
||||||
|
label={label}
|
||||||
|
name={name}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
FormikCheckbox.propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
id: PropTypes.string,
|
||||||
|
label: PropTypes.string,
|
||||||
|
name: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = FormikCheckbox;
|
39
src/components/formik-forms/formik-checkbox.scss
Normal file
39
src/components/formik-forms/formik-checkbox.scss
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
@import "../../colors";
|
||||||
|
|
||||||
|
.formik-checkbox-label {
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"].formik-checkbox {
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
margin-right: .625rem;
|
||||||
|
border: 1px solid $active-dark-gray;
|
||||||
|
border-radius: 3px;
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
appearance: none;
|
||||||
|
|
||||||
|
&:focus:checked {
|
||||||
|
transition: all .5s ease;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 .25rem $ui-blue-25percent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus:not(:checked) {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked {
|
||||||
|
background-color: $ui-blue;
|
||||||
|
text-align: center;
|
||||||
|
text-indent: .125rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
font-size: .75rem;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
color: $type-white;
|
||||||
|
content: "\2714";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
src/components/formik-forms/formik-forms.scss
Normal file
3
src/components/formik-forms/formik-forms.scss
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.formik-label {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
|
@ -5,8 +5,8 @@ import {Field} from 'formik';
|
||||||
|
|
||||||
const ValidationMessage = require('../forms/validation-message.jsx');
|
const ValidationMessage = require('../forms/validation-message.jsx');
|
||||||
|
|
||||||
require('../forms/input.scss');
|
|
||||||
require('../forms/row.scss');
|
require('../forms/row.scss');
|
||||||
|
require('./formik-input.scss');
|
||||||
|
|
||||||
const FormikInput = ({
|
const FormikInput = ({
|
||||||
className,
|
className,
|
||||||
|
@ -26,6 +26,7 @@ const FormikInput = ({
|
||||||
<Field
|
<Field
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'input',
|
'input',
|
||||||
|
{fail: error},
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
28
src/components/formik-forms/formik-input.scss
Normal file
28
src/components/formik-forms/formik-input.scss
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
@import "../../colors";
|
||||||
|
|
||||||
|
.input {
|
||||||
|
height: 2.75rem;
|
||||||
|
border-radius: .5rem;
|
||||||
|
background-color: $ui-white;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
transition: all .5s ease;
|
||||||
|
border: 1px solid $active-gray;
|
||||||
|
padding: 0 1rem;
|
||||||
|
color: $type-gray;
|
||||||
|
font-size: .875rem;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0 0 0 .25rem $ui-blue-25percent;
|
||||||
|
outline: none;
|
||||||
|
border: 1px solid $ui-blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.fail {
|
||||||
|
border: 1px solid $ui-orange;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0 0 0 .25rem $ui-orange-25percent;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import {Field} from 'formik';
|
||||||
|
|
||||||
const FormikInput = require('./formik-input.jsx');
|
const FormikInput = require('./formik-input.jsx');
|
||||||
|
|
||||||
|
require('./formik-forms.scss');
|
||||||
require('./formik-radio-button.scss');
|
require('./formik-radio-button.scss');
|
||||||
require('../forms/row.scss');
|
require('../forms/row.scss');
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ const FormikRadioButtonSubComponent = ({
|
||||||
{label && (
|
{label && (
|
||||||
<label
|
<label
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
'formik-label',
|
||||||
'formik-radio-label',
|
'formik-radio-label',
|
||||||
labelClassName
|
labelClassName
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
@import "../../colors";
|
@import "../../colors";
|
||||||
|
|
||||||
.formik-radio-label {
|
.formik-radio-label {
|
||||||
font-weight: 300;
|
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ const FormikSelect = ({
|
||||||
}) => {
|
}) => {
|
||||||
const optionsList = options.map((item, index) => (
|
const optionsList = options.map((item, index) => (
|
||||||
<option
|
<option
|
||||||
|
disabled={item.disabled}
|
||||||
key={index}
|
key={index}
|
||||||
value={item.value}
|
value={item.value}
|
||||||
>
|
>
|
||||||
|
|
7
src/components/formik-forms/input.scss
Normal file
7
src/components/formik-forms/input.scss
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
@import "../../colors";
|
||||||
|
@import "../../frameless";
|
||||||
|
|
||||||
|
.input::placeholder {
|
||||||
|
font-style: italic;
|
||||||
|
color: $type-gray-75percent;
|
||||||
|
}
|
46
src/components/info-button/info-button.jsx
Normal file
46
src/components/info-button/info-button.jsx
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
const bindAll = require('lodash.bindall');
|
||||||
|
const PropTypes = require('prop-types');
|
||||||
|
const React = require('react');
|
||||||
|
|
||||||
|
require('./info-button.scss');
|
||||||
|
|
||||||
|
class InfoButton extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
bindAll(this, [
|
||||||
|
'handleHideMessage',
|
||||||
|
'handleShowMessage'
|
||||||
|
]);
|
||||||
|
this.state = {
|
||||||
|
visible: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
handleHideMessage () {
|
||||||
|
this.setState({visible: false});
|
||||||
|
}
|
||||||
|
handleShowMessage () {
|
||||||
|
this.setState({visible: true});
|
||||||
|
}
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="info-button"
|
||||||
|
onClick={this.handleShowMessage}
|
||||||
|
onMouseOut={this.handleHideMessage}
|
||||||
|
onMouseOver={this.handleShowMessage}
|
||||||
|
>
|
||||||
|
{this.state.visible && (
|
||||||
|
<div className="info-button-message">
|
||||||
|
{this.props.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoButton.propTypes = {
|
||||||
|
message: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = InfoButton;
|
78
src/components/info-button/info-button.scss
Normal file
78
src/components/info-button/info-button.scss
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
@import "../../colors";
|
||||||
|
@import "../../frameless";
|
||||||
|
|
||||||
|
.info-button {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
margin-left: .375rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: $ui-blue;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
position: absolute;
|
||||||
|
content: "?";
|
||||||
|
color: $ui-white;
|
||||||
|
font-family: verdana;
|
||||||
|
font-weight: 400;
|
||||||
|
top: -.125rem;
|
||||||
|
left: .325rem;
|
||||||
|
font-size: .75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-button-message {
|
||||||
|
$arrow-border-width: 1rem;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
transform: translate(1rem, -1rem);
|
||||||
|
width: 16.5rem;
|
||||||
|
min-height: 1rem;
|
||||||
|
margin-left: $arrow-border-width;
|
||||||
|
border: 1px solid $active-gray;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: .75rem;
|
||||||
|
overflow: visible;
|
||||||
|
background-color: $ui-blue;
|
||||||
|
color: $type-white;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
text-align: left;
|
||||||
|
font-size: .875rem;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
left: -$arrow-border-width / 2;
|
||||||
|
|
||||||
|
transform: rotate(45deg);
|
||||||
|
|
||||||
|
border-bottom: 1px solid $active-gray;
|
||||||
|
border-left: 1px solid $active-gray;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
background-color: $ui-blue;
|
||||||
|
width: $arrow-border-width;
|
||||||
|
height: $arrow-border-width;
|
||||||
|
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media #{$intermediate-and-smaller} {
|
||||||
|
.info-button-message {
|
||||||
|
position: relative;
|
||||||
|
transform: none;
|
||||||
|
margin: inherit;
|
||||||
|
width: 100%;
|
||||||
|
height: inherit;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,7 +53,7 @@ class BirthDateStep extends React.Component {
|
||||||
}
|
}
|
||||||
validateSelect (selection) {
|
validateSelect (selection) {
|
||||||
if (selection === 'null') {
|
if (selection === 'null') {
|
||||||
return this.props.intl.formatMessage({id: 'form.validationRequired'});
|
return this.props.intl.formatMessage({id: 'general.required'});
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -88,6 +88,7 @@ class BirthDateStep extends React.Component {
|
||||||
<JoinFlowStep
|
<JoinFlowStep
|
||||||
description={this.props.intl.formatMessage({id: 'registration.private'})}
|
description={this.props.intl.formatMessage({id: 'registration.private'})}
|
||||||
headerImgSrc="/images/hoc/getting-started.jpg"
|
headerImgSrc="/images/hoc/getting-started.jpg"
|
||||||
|
infoMessage={this.props.intl.formatMessage({id: 'registration.birthDateStepInfo'})}
|
||||||
title={this.props.intl.formatMessage({id: 'registration.birthDateStepTitle'})}
|
title={this.props.intl.formatMessage({id: 'registration.birthDateStepTitle'})}
|
||||||
waiting={isSubmitting}
|
waiting={isSubmitting}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
|
|
111
src/components/join-flow/country-step.jsx
Normal file
111
src/components/join-flow/country-step.jsx
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
const bindAll = require('lodash.bindall');
|
||||||
|
const classNames = require('classnames');
|
||||||
|
const React = require('react');
|
||||||
|
const PropTypes = require('prop-types');
|
||||||
|
import {Formik} from 'formik';
|
||||||
|
const {injectIntl, intlShape} = require('react-intl');
|
||||||
|
|
||||||
|
const countryData = require('../../lib/country-data');
|
||||||
|
const FormikSelect = require('../../components/formik-forms/formik-select.jsx');
|
||||||
|
const JoinFlowStep = require('./join-flow-step.jsx');
|
||||||
|
|
||||||
|
require('./join-flow-steps.scss');
|
||||||
|
|
||||||
|
class CountryStep extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
bindAll(this, [
|
||||||
|
'handleValidSubmit',
|
||||||
|
'validateForm',
|
||||||
|
'validateSelect'
|
||||||
|
]);
|
||||||
|
this.countryOptions = [];
|
||||||
|
}
|
||||||
|
componentDidMount () {
|
||||||
|
this.setCountryOptions();
|
||||||
|
}
|
||||||
|
setCountryOptions () {
|
||||||
|
if (this.countryOptions.length === 0) {
|
||||||
|
this.countryOptions = [...countryData.registrationCountryOptions];
|
||||||
|
this.countryOptions.unshift({
|
||||||
|
disabled: true,
|
||||||
|
label: this.props.intl.formatMessage({id: 'registration.selectCountry'}),
|
||||||
|
value: 'null'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validateSelect (selection) {
|
||||||
|
if (selection === 'null') {
|
||||||
|
return this.props.intl.formatMessage({id: 'general.required'});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
validateForm () {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
handleValidSubmit (formData, formikBag) {
|
||||||
|
formikBag.setSubmitting(false);
|
||||||
|
this.props.onNextStep(formData);
|
||||||
|
}
|
||||||
|
render () {
|
||||||
|
this.setCountryOptions();
|
||||||
|
return (
|
||||||
|
<Formik
|
||||||
|
initialValues={{
|
||||||
|
country: 'null'
|
||||||
|
}}
|
||||||
|
validate={this.validateForm}
|
||||||
|
validateOnBlur={false}
|
||||||
|
validateOnChange={false}
|
||||||
|
onSubmit={this.handleValidSubmit}
|
||||||
|
>
|
||||||
|
{props => {
|
||||||
|
const {
|
||||||
|
errors,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting
|
||||||
|
} = props;
|
||||||
|
return (
|
||||||
|
<JoinFlowStep
|
||||||
|
description={this.props.intl.formatMessage({id: 'registration.countryStepDescription'})}
|
||||||
|
headerImgSrc="/images/hoc/getting-started.jpg"
|
||||||
|
title={this.props.intl.formatMessage({id: 'registration.countryStepTitle'})}
|
||||||
|
waiting={isSubmitting}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'col-sm-9',
|
||||||
|
'row'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<FormikSelect
|
||||||
|
className={classNames(
|
||||||
|
'join-flow-select',
|
||||||
|
'join-flow-select-country',
|
||||||
|
{fail: errors.country}
|
||||||
|
)}
|
||||||
|
error={errors.country}
|
||||||
|
id="country"
|
||||||
|
name="country"
|
||||||
|
options={this.countryOptions}
|
||||||
|
validate={this.validateSelect}
|
||||||
|
validationClassName="validation-full-width-input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</JoinFlowStep>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CountryStep.propTypes = {
|
||||||
|
intl: intlShape,
|
||||||
|
onNextStep: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
const IntlCountryStep = injectIntl(CountryStep);
|
||||||
|
|
||||||
|
module.exports = IntlCountryStep;
|
|
@ -1,10 +1,14 @@
|
||||||
const bindAll = require('lodash.bindall');
|
const bindAll = require('lodash.bindall');
|
||||||
|
const classNames = require('classnames');
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const PropTypes = require('prop-types');
|
const PropTypes = require('prop-types');
|
||||||
import {Formik} from 'formik';
|
import {Formik} from 'formik';
|
||||||
const {injectIntl, intlShape} = require('react-intl');
|
const {injectIntl, intlShape} = require('react-intl');
|
||||||
|
const emailValidator = require('email-validator');
|
||||||
|
const FormattedMessage = require('react-intl').FormattedMessage;
|
||||||
|
|
||||||
const JoinFlowStep = require('./join-flow-step.jsx');
|
const JoinFlowStep = require('./join-flow-step.jsx');
|
||||||
|
const FormikInput = require('../../components/formik-forms/formik-input.jsx');
|
||||||
|
|
||||||
require('./join-flow-steps.scss');
|
require('./join-flow-steps.scss');
|
||||||
|
|
||||||
|
@ -13,9 +17,18 @@ class EmailStep extends React.Component {
|
||||||
super(props);
|
super(props);
|
||||||
bindAll(this, [
|
bindAll(this, [
|
||||||
'handleValidSubmit',
|
'handleValidSubmit',
|
||||||
|
'validateEmailIfPresent',
|
||||||
'validateForm'
|
'validateForm'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
validateEmailIfPresent (email) {
|
||||||
|
if (!email) return null; // skip validation if email is blank; null indicates valid
|
||||||
|
const isValidLocally = emailValidator.validate(email);
|
||||||
|
if (isValidLocally) {
|
||||||
|
return null; // TODO: validate email address remotely
|
||||||
|
}
|
||||||
|
return this.props.intl.formatMessage({id: 'registration.validationEmailInvalid'});
|
||||||
|
}
|
||||||
validateForm () {
|
validateForm () {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -35,17 +48,52 @@ class EmailStep extends React.Component {
|
||||||
>
|
>
|
||||||
{props => {
|
{props => {
|
||||||
const {
|
const {
|
||||||
|
errors,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
isSubmitting
|
isSubmitting,
|
||||||
|
validateField
|
||||||
} = props;
|
} = props;
|
||||||
return (
|
return (
|
||||||
<JoinFlowStep
|
<JoinFlowStep
|
||||||
description={this.props.intl.formatMessage({id: 'registration.emailStepDescription'})}
|
description={this.props.intl.formatMessage({id: 'registration.emailStepDescription'})}
|
||||||
|
footerContent={(
|
||||||
|
<FormattedMessage
|
||||||
|
id="registration.acceptTermsOfUse"
|
||||||
|
values={{
|
||||||
|
touLink: (
|
||||||
|
<a
|
||||||
|
className="join-flow-link"
|
||||||
|
href="/terms_of_use"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<FormattedMessage id="general.termsOfUse" />
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
headerImgSrc="/images/hoc/getting-started.jpg"
|
headerImgSrc="/images/hoc/getting-started.jpg"
|
||||||
|
innerContentClassName="modal-inner-content-email"
|
||||||
|
nextButton={this.props.intl.formatMessage({id: 'registration.createAccount'})}
|
||||||
title={this.props.intl.formatMessage({id: 'registration.emailStepTitle'})}
|
title={this.props.intl.formatMessage({id: 'registration.emailStepTitle'})}
|
||||||
waiting={isSubmitting}
|
waiting={isSubmitting}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
/>
|
>
|
||||||
|
<FormikInput
|
||||||
|
className={classNames(
|
||||||
|
'join-flow-input',
|
||||||
|
'join-flow-input-tall',
|
||||||
|
{fail: errors.email}
|
||||||
|
)}
|
||||||
|
error={errors.email}
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
placeholder={this.props.intl.formatMessage({id: 'general.emailAddress'})}
|
||||||
|
validate={this.validateEmailIfPresent}
|
||||||
|
validationClassName="validation-full-width-input"
|
||||||
|
onBlur={() => validateField('email')} // eslint-disable-line react/jsx-no-bind
|
||||||
|
/>
|
||||||
|
</JoinFlowStep>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Formik>
|
</Formik>
|
||||||
|
@ -58,4 +106,5 @@ EmailStep.propTypes = {
|
||||||
onNextStep: PropTypes.func
|
onNextStep: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
module.exports = injectIntl(EmailStep);
|
module.exports = injectIntl(EmailStep);
|
||||||
|
|
|
@ -82,6 +82,7 @@ class GenderStep extends React.Component {
|
||||||
<JoinFlowStep
|
<JoinFlowStep
|
||||||
className="join-flow-gender-step"
|
className="join-flow-gender-step"
|
||||||
description={this.props.intl.formatMessage({id: 'registration.genderStepDescription'})}
|
description={this.props.intl.formatMessage({id: 'registration.genderStepDescription'})}
|
||||||
|
infoMessage={this.props.intl.formatMessage({id: 'registration.genderStepInfo'})}
|
||||||
title={this.props.intl.formatMessage({id: 'registration.genderStepTitle'})}
|
title={this.props.intl.formatMessage({id: 'registration.genderStepTitle'})}
|
||||||
waiting={isSubmitting}
|
waiting={isSubmitting}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
|
|
|
@ -5,6 +5,7 @@ const PropTypes = require('prop-types');
|
||||||
const NextStepButton = require('./next-step-button.jsx');
|
const NextStepButton = require('./next-step-button.jsx');
|
||||||
const ModalTitle = require('../modal/base/modal-title.jsx');
|
const ModalTitle = require('../modal/base/modal-title.jsx');
|
||||||
const ModalInnerContent = require('../modal/base/modal-inner-content.jsx');
|
const ModalInnerContent = require('../modal/base/modal-inner-content.jsx');
|
||||||
|
const InfoButton = require('../info-button/info-button.jsx');
|
||||||
|
|
||||||
require('./join-flow-step.scss');
|
require('./join-flow-step.scss');
|
||||||
|
|
||||||
|
@ -12,7 +13,10 @@ const JoinFlowStep = ({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
description,
|
description,
|
||||||
|
footerContent,
|
||||||
headerImgSrc,
|
headerImgSrc,
|
||||||
|
infoMessage,
|
||||||
|
innerContentClassName,
|
||||||
nextButton,
|
nextButton,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
title,
|
title,
|
||||||
|
@ -28,7 +32,8 @@ const JoinFlowStep = ({
|
||||||
<ModalInnerContent
|
<ModalInnerContent
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'join-flow-inner-content',
|
'join-flow-inner-content',
|
||||||
className
|
className,
|
||||||
|
innerContentClassName
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{title && (
|
{title && (
|
||||||
|
@ -40,11 +45,19 @@ const JoinFlowStep = ({
|
||||||
{description && (
|
{description && (
|
||||||
<div className="join-flow-description">
|
<div className="join-flow-description">
|
||||||
{description}
|
{description}
|
||||||
|
{infoMessage && (
|
||||||
|
<InfoButton message={infoMessage} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{children}
|
{children}
|
||||||
</ModalInnerContent>
|
</ModalInnerContent>
|
||||||
</div>
|
</div>
|
||||||
|
{footerContent && (
|
||||||
|
<div className="join-flow-footer-message">
|
||||||
|
{footerContent}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<NextStepButton
|
<NextStepButton
|
||||||
content={nextButton}
|
content={nextButton}
|
||||||
waiting={waiting}
|
waiting={waiting}
|
||||||
|
@ -56,7 +69,10 @@ JoinFlowStep.propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
description: PropTypes.string,
|
description: PropTypes.string,
|
||||||
|
footerContent: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||||
headerImgSrc: PropTypes.string,
|
headerImgSrc: PropTypes.string,
|
||||||
|
infoMessage: PropTypes.string,
|
||||||
|
innerContentClassName: PropTypes.string,
|
||||||
nextButton: PropTypes.node,
|
nextButton: PropTypes.node,
|
||||||
onSubmit: PropTypes.func,
|
onSubmit: PropTypes.func,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
|
|
|
@ -33,3 +33,13 @@
|
||||||
border-top-left-radius: 1rem;
|
border-top-left-radius: 1rem;
|
||||||
border-top-right-radius: 1rem;
|
border-top-right-radius: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.join-flow-footer-message {
|
||||||
|
width: 100%;
|
||||||
|
padding: 1.125rem 1.5rem 1.125rem;
|
||||||
|
background-color: $ui-blue-25percent;
|
||||||
|
font-size: .75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
color: $ui-blue;
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.join-flow-password-confirm {
|
||||||
|
margin-bottom: .6875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.join-flow-input-tall {
|
||||||
|
height: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
.join-flow-input-title {
|
.join-flow-input-title {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: .5rem;
|
margin-bottom: .5rem;
|
||||||
|
@ -47,12 +55,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.select .join-flow-select {
|
.select .join-flow-select-month {
|
||||||
width: 9.125rem;
|
width: 9.125rem;
|
||||||
|
margin-right: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.join-flow-select-month {
|
.select .join-flow-select-country {
|
||||||
margin-right: .5rem;
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.join-flow-password-section {
|
.join-flow-password-section {
|
||||||
|
@ -91,3 +101,11 @@
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
margin-left: .5rem;
|
margin-left: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-inner-content-email {
|
||||||
|
padding-top: 2.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.join-flow-link:link, a.join-flow-link:visited, a.join-flow-link:active {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ const Progression = require('../progression/progression.jsx');
|
||||||
const UsernameStep = require('./username-step.jsx');
|
const UsernameStep = require('./username-step.jsx');
|
||||||
const BirthDateStep = require('./birthdate-step.jsx');
|
const BirthDateStep = require('./birthdate-step.jsx');
|
||||||
const GenderStep = require('./gender-step.jsx');
|
const GenderStep = require('./gender-step.jsx');
|
||||||
|
const CountryStep = require('./country-step.jsx');
|
||||||
const EmailStep = require('./email-step.jsx');
|
const EmailStep = require('./email-step.jsx');
|
||||||
const WelcomeStep = require('./welcome-step.jsx');
|
const WelcomeStep = require('./welcome-step.jsx');
|
||||||
|
|
||||||
|
@ -42,6 +43,7 @@ class JoinFlow extends React.Component {
|
||||||
<UsernameStep onNextStep={this.handleAdvanceStep} />
|
<UsernameStep onNextStep={this.handleAdvanceStep} />
|
||||||
<BirthDateStep onNextStep={this.handleAdvanceStep} />
|
<BirthDateStep onNextStep={this.handleAdvanceStep} />
|
||||||
<GenderStep onNextStep={this.handleAdvanceStep} />
|
<GenderStep onNextStep={this.handleAdvanceStep} />
|
||||||
|
<CountryStep onNextStep={this.handleAdvanceStep} />
|
||||||
<EmailStep onNextStep={this.handleAdvanceStep} />
|
<EmailStep onNextStep={this.handleAdvanceStep} />
|
||||||
<WelcomeStep
|
<WelcomeStep
|
||||||
email={this.state.formData.email}
|
email={this.state.formData.email}
|
||||||
|
|
|
@ -7,6 +7,7 @@ const {injectIntl, intlShape} = require('react-intl');
|
||||||
|
|
||||||
const validate = require('../../lib/validate');
|
const validate = require('../../lib/validate');
|
||||||
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 JoinFlowStep = require('./join-flow-step.jsx');
|
const JoinFlowStep = require('./join-flow-step.jsx');
|
||||||
|
|
||||||
require('./join-flow-steps.scss');
|
require('./join-flow-steps.scss');
|
||||||
|
@ -26,9 +27,6 @@ class UsernameStep extends React.Component {
|
||||||
'validateUsernameIfPresent',
|
'validateUsernameIfPresent',
|
||||||
'validateForm'
|
'validateForm'
|
||||||
]);
|
]);
|
||||||
this.state = {
|
|
||||||
showPassword: false
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
handleChangeShowPassword () {
|
handleChangeShowPassword () {
|
||||||
this.setState({showPassword: !this.state.showPassword});
|
this.setState({showPassword: !this.state.showPassword});
|
||||||
|
@ -36,20 +34,26 @@ class UsernameStep extends React.Component {
|
||||||
// we allow username to be empty on blur, since you might not have typed anything yet
|
// we allow username to be empty on blur, since you might not have typed anything yet
|
||||||
validateUsernameIfPresent (username) {
|
validateUsernameIfPresent (username) {
|
||||||
if (!username) return null; // skip validation if username is blank; null indicates valid
|
if (!username) return null; // skip validation if username is blank; null indicates valid
|
||||||
|
// if username is not blank, run both local and remote validations
|
||||||
const localResult = validate.validateUsernameLocally(username);
|
const localResult = validate.validateUsernameLocally(username);
|
||||||
if (localResult.valid) {
|
return validate.validateUsernameRemotely(username).then(
|
||||||
return validate.validateUsernameRemotely(username).then(
|
remoteResult => {
|
||||||
remoteResult => {
|
// there may be multiple validation errors. Prioritize vulgarity, then
|
||||||
if (remoteResult.valid) return null;
|
// length, then having invalid chars, then all other remote reports
|
||||||
|
if (remoteResult.valid === false && remoteResult.errMsgId === 'registration.validationUsernameVulgar') {
|
||||||
|
return this.props.intl.formatMessage({id: remoteResult.errMsgId});
|
||||||
|
} else if (localResult.valid === false) {
|
||||||
|
return this.props.intl.formatMessage({id: localResult.errMsgId});
|
||||||
|
} else if (remoteResult.valid === false) {
|
||||||
return this.props.intl.formatMessage({id: remoteResult.errMsgId});
|
return this.props.intl.formatMessage({id: remoteResult.errMsgId});
|
||||||
}
|
}
|
||||||
);
|
return null;
|
||||||
}
|
}
|
||||||
return this.props.intl.formatMessage({id: localResult.errMsgId});
|
);
|
||||||
}
|
}
|
||||||
validatePasswordIfPresent (password) {
|
validatePasswordIfPresent (password, username) {
|
||||||
if (!password) return null; // skip validation if password is blank; null indicates valid
|
if (!password) return null; // skip validation if password is blank; null indicates valid
|
||||||
const localResult = validate.validatePassword(password);
|
const localResult = validate.validatePassword(password, username);
|
||||||
if (localResult.valid) return null;
|
if (localResult.valid) return null;
|
||||||
return this.props.intl.formatMessage({id: localResult.errMsgId});
|
return this.props.intl.formatMessage({id: localResult.errMsgId});
|
||||||
}
|
}
|
||||||
|
@ -69,13 +73,10 @@ class UsernameStep extends React.Component {
|
||||||
if (!usernameResult.valid) {
|
if (!usernameResult.valid) {
|
||||||
errors.username = this.props.intl.formatMessage({id: usernameResult.errMsgId});
|
errors.username = this.props.intl.formatMessage({id: usernameResult.errMsgId});
|
||||||
}
|
}
|
||||||
const passwordResult = validate.validatePassword(values.password);
|
const passwordResult = validate.validatePassword(values.password, values.username);
|
||||||
if (!passwordResult.valid) {
|
if (!passwordResult.valid) {
|
||||||
errors.password = this.props.intl.formatMessage({id: passwordResult.errMsgId});
|
errors.password = this.props.intl.formatMessage({id: passwordResult.errMsgId});
|
||||||
}
|
}
|
||||||
if (values.password === values.username) {
|
|
||||||
errors.password = this.props.intl.formatMessage({id: 'registration.validationPasswordNotUsername'});
|
|
||||||
}
|
|
||||||
const passwordConfirmResult = validate.validatePasswordConfirm(values.password, values.passwordConfirm);
|
const passwordConfirmResult = validate.validatePasswordConfirm(values.password, values.passwordConfirm);
|
||||||
if (!passwordConfirmResult.valid) {
|
if (!passwordConfirmResult.valid) {
|
||||||
errors.passwordConfirm = this.props.intl.formatMessage({id: passwordConfirmResult.errMsgId});
|
errors.passwordConfirm = this.props.intl.formatMessage({id: passwordConfirmResult.errMsgId});
|
||||||
|
@ -85,6 +86,7 @@ class UsernameStep extends React.Component {
|
||||||
// called after all validations pass with no errors
|
// called after all validations pass with no errors
|
||||||
handleValidSubmit (formData, formikBag) {
|
handleValidSubmit (formData, formikBag) {
|
||||||
formikBag.setSubmitting(false); // formik makes us do this ourselves
|
formikBag.setSubmitting(false); // formik makes us do this ourselves
|
||||||
|
delete formData.showPassword;
|
||||||
this.props.onNextStep(formData);
|
this.props.onNextStep(formData);
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
|
@ -93,7 +95,8 @@ class UsernameStep extends React.Component {
|
||||||
initialValues={{
|
initialValues={{
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
passwordConfirm: ''
|
passwordConfirm: '',
|
||||||
|
showPassword: false
|
||||||
}}
|
}}
|
||||||
validate={this.validateForm}
|
validate={this.validateForm}
|
||||||
validateOnBlur={false}
|
validateOnBlur={false}
|
||||||
|
@ -105,6 +108,8 @@ class UsernameStep extends React.Component {
|
||||||
errors,
|
errors,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
|
setFieldError,
|
||||||
|
setFieldValue,
|
||||||
validateField,
|
validateField,
|
||||||
values
|
values
|
||||||
} = props;
|
} = props;
|
||||||
|
@ -123,15 +128,20 @@ class UsernameStep extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
<FormikInput
|
<FormikInput
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'join-flow-input',
|
'join-flow-input'
|
||||||
{fail: errors.username}
|
|
||||||
)}
|
)}
|
||||||
error={errors.username}
|
error={errors.username}
|
||||||
id="username"
|
id="username"
|
||||||
name="username"
|
name="username"
|
||||||
validate={this.validateUsernameIfPresent}
|
validate={this.validateUsernameIfPresent}
|
||||||
validationClassName="validation-full-width-input"
|
validationClassName="validation-full-width-input"
|
||||||
onBlur={() => validateField('username')} // eslint-disable-line react/jsx-no-bind
|
/* eslint-disable react/jsx-no-bind */
|
||||||
|
onBlur={() => validateField('username')}
|
||||||
|
onChange={e => {
|
||||||
|
setFieldValue('username', e.target.value);
|
||||||
|
setFieldError('username', null);
|
||||||
|
}}
|
||||||
|
/* eslint-enable react/jsx-no-bind */
|
||||||
/>
|
/>
|
||||||
<div className="join-flow-password-section">
|
<div className="join-flow-password-section">
|
||||||
<div className="join-flow-input-title">
|
<div className="join-flow-input-title">
|
||||||
|
@ -139,28 +149,32 @@ class UsernameStep extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
<FormikInput
|
<FormikInput
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'join-flow-input',
|
'join-flow-input'
|
||||||
{fail: errors.password}
|
|
||||||
)}
|
)}
|
||||||
error={errors.password}
|
error={errors.password}
|
||||||
id="password"
|
id="password"
|
||||||
name="password"
|
name="password"
|
||||||
type={this.state.showPassword ? 'text' : 'password'}
|
type={values.showPassword ? 'text' : 'password'}
|
||||||
validate={this.validatePasswordIfPresent}
|
|
||||||
validationClassName="validation-full-width-input"
|
|
||||||
/* eslint-disable react/jsx-no-bind */
|
/* eslint-disable react/jsx-no-bind */
|
||||||
|
validate={password => this.validatePasswordIfPresent(password, values.username)}
|
||||||
|
validationClassName="validation-full-width-input"
|
||||||
onBlur={() => validateField('password')}
|
onBlur={() => validateField('password')}
|
||||||
|
onChange={e => {
|
||||||
|
setFieldValue('password', e.target.value);
|
||||||
|
setFieldError('password', null);
|
||||||
|
}}
|
||||||
/* eslint-enable react/jsx-no-bind */
|
/* eslint-enable react/jsx-no-bind */
|
||||||
/>
|
/>
|
||||||
<FormikInput
|
<FormikInput
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'join-flow-input',
|
'join-flow-input',
|
||||||
|
'join-flow-password-confirm',
|
||||||
{fail: errors.passwordConfirm}
|
{fail: errors.passwordConfirm}
|
||||||
)}
|
)}
|
||||||
error={errors.passwordConfirm}
|
error={errors.passwordConfirm}
|
||||||
id="passwordConfirm"
|
id="passwordConfirm"
|
||||||
name="passwordConfirm"
|
name="passwordConfirm"
|
||||||
type={this.state.showPassword ? 'text' : 'password'}
|
type={values.showPassword ? 'text' : 'password'}
|
||||||
/* eslint-disable react/jsx-no-bind */
|
/* eslint-disable react/jsx-no-bind */
|
||||||
validate={() =>
|
validate={() =>
|
||||||
this.validatePasswordConfirmIfPresent(values.password,
|
this.validatePasswordConfirmIfPresent(values.password,
|
||||||
|
@ -170,17 +184,18 @@ class UsernameStep extends React.Component {
|
||||||
onBlur={() =>
|
onBlur={() =>
|
||||||
validateField('passwordConfirm')
|
validateField('passwordConfirm')
|
||||||
}
|
}
|
||||||
|
onChange={e => {
|
||||||
|
setFieldValue('passwordConfirm', e.target.value);
|
||||||
|
setFieldError('passwordConfirm', null);
|
||||||
|
}}
|
||||||
/* eslint-enable react/jsx-no-bind */
|
/* eslint-enable react/jsx-no-bind */
|
||||||
/>
|
/>
|
||||||
<div className="join-flow-input-title">
|
<div className="join-flow-input-title">
|
||||||
<div
|
<FormikCheckbox
|
||||||
onClick={this.handleChangeShowPassword}
|
id="showPassword"
|
||||||
>
|
label={this.props.intl.formatMessage({id: 'registration.showPassword'})}
|
||||||
{/* TODO: should localize 'Hide password' if we use that */}
|
name="showPassword"
|
||||||
{this.state.showPassword ? 'Hide password' : (
|
/>
|
||||||
this.props.intl.formatMessage({id: 'registration.showPassword'})
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -139,7 +139,7 @@ class UsernameStep extends React.Component {
|
||||||
default:
|
default:
|
||||||
this.form.formsy.updateInputsWithError({
|
this.form.formsy.updateInputsWithError({
|
||||||
'user.username': this.props.intl.formatMessage({
|
'user.username': this.props.intl.formatMessage({
|
||||||
id: 'registration.validationUsernameInvalid'
|
id: 'registration.validationUsernameNotAllowed'
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
return callback(false);
|
return callback(false);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"general.accountSettings": "Account settings",
|
"general.accountSettings": "Account settings",
|
||||||
"general.about": "About",
|
"general.about": "About",
|
||||||
"general.aboutScratch": "About Scratch",
|
"general.aboutScratch": "About Scratch",
|
||||||
|
"general.apiError": "Whoops, Scratch had an error.",
|
||||||
"general.back": "Back",
|
"general.back": "Back",
|
||||||
"general.birthMonth": "Birth Month",
|
"general.birthMonth": "Birth Month",
|
||||||
"general.birthYear": "Birth Year",
|
"general.birthYear": "Birth Year",
|
||||||
|
@ -20,7 +21,7 @@
|
||||||
"general.create": "Create",
|
"general.create": "Create",
|
||||||
"general.credits": "Credits",
|
"general.credits": "Credits",
|
||||||
"general.dmca": "DMCA",
|
"general.dmca": "DMCA",
|
||||||
"general.emailAddress": "Email Address",
|
"general.emailAddress": "Email address",
|
||||||
"general.english": "English",
|
"general.english": "English",
|
||||||
"general.error": "Oops! Something went wrong",
|
"general.error": "Oops! Something went wrong",
|
||||||
"general.errorIdentifier": "Your error was logged with id {errorId}",
|
"general.errorIdentifier": "Your error was logged with id {errorId}",
|
||||||
|
@ -70,6 +71,7 @@
|
||||||
"general.privacyPolicy": "Privacy Policy",
|
"general.privacyPolicy": "Privacy Policy",
|
||||||
"general.projects": "Projects",
|
"general.projects": "Projects",
|
||||||
"general.profile": "Profile",
|
"general.profile": "Profile",
|
||||||
|
"general.required": "Required",
|
||||||
"general.resourcesTitle": "Educator Resources",
|
"general.resourcesTitle": "Educator Resources",
|
||||||
"general.scratchConference": "Scratch Conference",
|
"general.scratchConference": "Scratch Conference",
|
||||||
"general.scratchEd": "ScratchEd",
|
"general.scratchEd": "ScratchEd",
|
||||||
|
@ -141,6 +143,7 @@
|
||||||
"parents.FaqResourcesQ": "What resources are available for learning Scratch?",
|
"parents.FaqResourcesQ": "What resources are available for learning Scratch?",
|
||||||
"parents.introDescription": "Scratch is a programming language and an online community where children can program and share interactive media such as stories, games, and animation with people from all over the world. As children create with Scratch, they learn to think creatively, work collaboratively, and reason systematically. Scratch is designed and maintained by the Lifelong Kindergarten group at the MIT Media Lab.",
|
"parents.introDescription": "Scratch is a programming language and an online community where children can program and share interactive media such as stories, games, and animation with people from all over the world. As children create with Scratch, they learn to think creatively, work collaboratively, and reason systematically. Scratch is designed and maintained by the Lifelong Kindergarten group at the MIT Media Lab.",
|
||||||
|
|
||||||
|
"registration.birthDateStepInfo": "This helps us understand the age range of people who use Scratch. We use this to confirm account ownership if you contact our team. This information will not be made public on your account.",
|
||||||
"registration.birthDateStepTitle": "When were you born?",
|
"registration.birthDateStepTitle": "When were you born?",
|
||||||
"registration.checkOutResources": "Get Started with Resources",
|
"registration.checkOutResources": "Get Started with Resources",
|
||||||
"registration.checkOutResourcesDescription": "Explore materials for educators and facilitators written by the Scratch Team, including <a href='/educators#resources'>tips, tutorials, and guides</a>.",
|
"registration.checkOutResourcesDescription": "Explore materials for educators and facilitators written by the Scratch Team, including <a href='/educators#resources'>tips, tutorials, and guides</a>.",
|
||||||
|
@ -148,14 +151,18 @@
|
||||||
"registration.choosePasswordStepTitle": "Create a password",
|
"registration.choosePasswordStepTitle": "Create a password",
|
||||||
"registration.choosePasswordStepTooltip": "Don't use your name or anything that's easy for someone else to guess.",
|
"registration.choosePasswordStepTooltip": "Don't use your name or anything that's easy for someone else to guess.",
|
||||||
"registration.classroomApiGeneralError": "Sorry, we could not find the registration information for this class",
|
"registration.classroomApiGeneralError": "Sorry, we could not find the registration information for this class",
|
||||||
|
"registration.countryStepDescription": "We’ll display your country on your profile.",
|
||||||
|
"registration.countryStepTitle": "What country do you live in?",
|
||||||
"registration.generalError": "Sorry, an unexpected error occurred.",
|
"registration.generalError": "Sorry, an unexpected error occurred.",
|
||||||
"registration.classroomInviteExistingStudentStepDescription": "you have been invited to join the class:",
|
"registration.classroomInviteExistingStudentStepDescription": "you have been invited to join the class:",
|
||||||
"registration.classroomInviteNewStudentStepDescription": "Your teacher has invited you to join a class:",
|
"registration.classroomInviteNewStudentStepDescription": "Your teacher has invited you to join a class:",
|
||||||
"registration.confirmYourEmail": "Confirm Your Email",
|
"registration.confirmYourEmail": "Confirm Your Email",
|
||||||
"registration.confirmYourEmailDescription": "If you haven't already, please click the link in the confirmation email sent to:",
|
"registration.confirmYourEmailDescription": "If you haven't already, please click the link in the confirmation email sent to:",
|
||||||
|
"registration.createAccount": "Create Account",
|
||||||
"registration.createUsername": "Create a username",
|
"registration.createUsername": "Create a username",
|
||||||
"registration.genderStepTitle": "What's your gender?",
|
"registration.genderStepTitle": "What's your gender?",
|
||||||
"registration.genderStepDescription": "Scratch welcomes people of all genders. We will always keep this information private.",
|
"registration.genderStepDescription": "Scratch welcomes people of all genders. We will always keep this information private.",
|
||||||
|
"registration.genderStepInfo": "This helps us understand who uses Scratch, so that we can broaden participation. This information will not be made public on your account.",
|
||||||
"registration.genderOptionAnother": "Another gender:",
|
"registration.genderOptionAnother": "Another gender:",
|
||||||
"registration.genderOptionPreferNotToSay": "Prefer not to say",
|
"registration.genderOptionPreferNotToSay": "Prefer not to say",
|
||||||
"registration.emailStepTitle": "What's your email?",
|
"registration.emailStepTitle": "What's your email?",
|
||||||
|
@ -184,18 +191,22 @@
|
||||||
"registration.studentUsernameStepHelpText": "Already have a Scratch account?",
|
"registration.studentUsernameStepHelpText": "Already have a Scratch account?",
|
||||||
"registration.studentUsernameStepTooltip": "You'll need to create a new Scratch account to join this class.",
|
"registration.studentUsernameStepTooltip": "You'll need to create a new Scratch account to join this class.",
|
||||||
"registration.studentUsernameFieldHelpText": "For safety, don't use your real name!",
|
"registration.studentUsernameFieldHelpText": "For safety, don't use your real name!",
|
||||||
|
"registration.acceptTermsOfUse": "By creating an account, I accept and agree to the {touLink}.",
|
||||||
"registration.usernameStepTitle": "Request a Teacher Account",
|
"registration.usernameStepTitle": "Request a Teacher Account",
|
||||||
"registration.usernameStepTitleScratcher": "Create a Scratch Account",
|
"registration.usernameStepTitleScratcher": "Create a Scratch Account",
|
||||||
"registration.validationMaxLength": "Sorry, you have exceeded the maximum character limit.",
|
"registration.validationMaxLength": "Sorry, you have exceeded the maximum character limit.",
|
||||||
"registration.validationPasswordLength": "Passwords must be at least six characters",
|
"registration.validationPasswordConfirmNotEquals": "Passwords don’t match",
|
||||||
"registration.validationPasswordNotEquals": "Your password may not be \"password\"",
|
"registration.validationPasswordLength": "Must be 6 letters or longer",
|
||||||
"registration.validationPasswordNotUsername": "Your password may not be your username",
|
"registration.validationPasswordNotEquals": "Password is too easy to guess. Try something else?",
|
||||||
"registration.validationUsernameRegexp": "Your username may only contain letters, numbers, \"-\", and \"_\"",
|
"registration.validationPasswordNotUsername": "Password can’t match your username",
|
||||||
"registration.validationUsernameMinLength": "Usernames must be at least 3 characters",
|
"registration.validationUsernameRegexp": "Usernames can only use letters, numbers, - and _",
|
||||||
"registration.validationUsernameMaxLength": "Usernames must be at most 20 characters",
|
"registration.validationUsernameMinLength": "Must be 3 letters or longer",
|
||||||
"registration.validationUsernameExists": "Sorry, that username already exists",
|
"registration.validationUsernameMaxLength": "Must be 20 letters or shorter",
|
||||||
|
"registration.validationUsernameExists": "Username taken. Try another?",
|
||||||
|
"registration.validationUsernameNotAllowed": "Username not allowed",
|
||||||
"registration.validationUsernameVulgar": "Hmm, that looks inappropriate",
|
"registration.validationUsernameVulgar": "Hmm, that looks inappropriate",
|
||||||
"registration.validationUsernameInvalid": "Invalid username",
|
"registration.validationUsernameInvalid": "Invalid username",
|
||||||
|
"registration.validationEmailInvalid": "Email doesn’t look right. Try another?",
|
||||||
"registration.waitForApproval": "Wait for Approval",
|
"registration.waitForApproval": "Wait for Approval",
|
||||||
"registration.waitForApprovalDescription": "You can log into your Scratch Account now, but the features specific to Teachers are not yet available. Your information is being reviewed. Please be patient, the approval process can take up to one day. You will receive an email indicating your account has been upgraded once your account has been approved.",
|
"registration.waitForApprovalDescription": "You can log into your Scratch Account now, but the features specific to Teachers are not yet available. Your information is being reviewed. Please be patient, the approval process can take up to one day. You will receive an email indicating your account has been upgraded once your account has been approved.",
|
||||||
"registration.welcomeStepDescription": "You have successfully set up a Scratch account! You are now a member of the class:",
|
"registration.welcomeStepDescription": "You have successfully set up a Scratch account! You are now a member of the class:",
|
||||||
|
|
|
@ -3,13 +3,13 @@ const api = require('./api');
|
||||||
|
|
||||||
module.exports.validateUsernameLocally = username => {
|
module.exports.validateUsernameLocally = username => {
|
||||||
if (!username || username === '') {
|
if (!username || username === '') {
|
||||||
return {valid: false, errMsgId: 'form.validationRequired'};
|
return {valid: false, errMsgId: 'general.required'};
|
||||||
} else if (username.length < 3) {
|
} else if (username.length < 3) {
|
||||||
return {valid: false, errMsgId: 'form.validationUsernameMinLength'};
|
return {valid: false, errMsgId: 'registration.validationUsernameMinLength'};
|
||||||
} else if (username.length > 20) {
|
} else if (username.length > 20) {
|
||||||
return {valid: false, errMsgId: 'form.validationUsernameMaxLength'};
|
return {valid: false, errMsgId: 'registration.validationUsernameMaxLength'};
|
||||||
} else if (!/^[\w-]+$/i.test(username)) {
|
} else if (!/^[\w-]+$/i.test(username)) {
|
||||||
return {valid: false, errMsgId: 'form.validationUsernameRegexp'};
|
return {valid: false, errMsgId: 'registration.validationUsernameRegexp'};
|
||||||
}
|
}
|
||||||
return {valid: true};
|
return {valid: true};
|
||||||
};
|
};
|
||||||
|
@ -29,34 +29,41 @@ module.exports.validateUsernameRemotely = username => (
|
||||||
case 'username exists':
|
case 'username exists':
|
||||||
resolve({valid: false, errMsgId: 'registration.validationUsernameExists'});
|
resolve({valid: false, errMsgId: 'registration.validationUsernameExists'});
|
||||||
break;
|
break;
|
||||||
case 'bad username':
|
case 'bad username': // i.e., vulgar
|
||||||
resolve({valid: false, errMsgId: 'registration.validationUsernameVulgar'});
|
resolve({valid: false, errMsgId: 'registration.validationUsernameNotAllowed'});
|
||||||
break;
|
break;
|
||||||
case 'invalid username':
|
case 'invalid username':
|
||||||
default:
|
default:
|
||||||
resolve({valid: false, errMsgId: 'registration.validationUsernameInvalid'});
|
resolve({valid: false, errMsgId: 'registration.validationUsernameNotAllowed'});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
module.exports.validatePassword = password => {
|
/**
|
||||||
|
* Validate password value, optionally also considering username value
|
||||||
|
* @param {string} password password value to validate
|
||||||
|
* @param {string} username username value to compare
|
||||||
|
* @return {object} {valid: boolean, errMsgId: string}
|
||||||
|
*/
|
||||||
|
module.exports.validatePassword = (password, username) => {
|
||||||
if (!password) {
|
if (!password) {
|
||||||
return {valid: false, errMsgId: 'form.validationRequired'};
|
return {valid: false, errMsgId: 'general.required'};
|
||||||
} else if (password.length < 6) {
|
} else if (password.length < 6) {
|
||||||
return {valid: false, errMsgId: 'registration.validationPasswordLength'};
|
return {valid: false, errMsgId: 'registration.validationPasswordLength'};
|
||||||
} else if (password === 'password') {
|
} else if (password === 'password') {
|
||||||
return {valid: false, errMsgId: 'registration.validationPasswordNotEquals'};
|
return {valid: false, errMsgId: 'registration.validationPasswordNotEquals'};
|
||||||
|
} else if (username && password === username) {
|
||||||
|
return {valid: false, errMsgId: 'registration.validationPasswordNotUsername'};
|
||||||
}
|
}
|
||||||
return {valid: true};
|
return {valid: true};
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.validatePasswordConfirm = (password, passwordConfirm) => {
|
module.exports.validatePasswordConfirm = (password, passwordConfirm) => {
|
||||||
if (!passwordConfirm) {
|
if (!passwordConfirm) {
|
||||||
return {valid: false, errMsgId: 'form.validationRequired'};
|
return {valid: false, errMsgId: 'general.required'};
|
||||||
} else if (password !== passwordConfirm) {
|
} else if (password !== passwordConfirm) {
|
||||||
// TODO: add a new string for this case
|
return {valid: false, errMsgId: 'registration.validationPasswordConfirmNotEquals'};
|
||||||
return {valid: false, errMsgId: 'general.error'};
|
|
||||||
}
|
}
|
||||||
return {valid: true};
|
return {valid: true};
|
||||||
};
|
};
|
||||||
|
|
|
@ -54,7 +54,11 @@ class Comment extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDelete () {
|
handleDelete () {
|
||||||
this.setState({deleting: true});
|
if (this.props.canDeleteWithoutConfirm) {
|
||||||
|
this.props.onDelete(this.props.id);
|
||||||
|
} else {
|
||||||
|
this.setState({deleting: true});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleConfirmDelete () {
|
handleConfirmDelete () {
|
||||||
|
@ -267,6 +271,7 @@ Comment.propTypes = {
|
||||||
username: PropTypes.string
|
username: PropTypes.string
|
||||||
}),
|
}),
|
||||||
canDelete: PropTypes.bool,
|
canDelete: PropTypes.bool,
|
||||||
|
canDeleteWithoutConfirm: PropTypes.bool,
|
||||||
canReply: PropTypes.bool,
|
canReply: PropTypes.bool,
|
||||||
canReport: PropTypes.bool,
|
canReport: PropTypes.bool,
|
||||||
canRestore: PropTypes.bool,
|
canRestore: PropTypes.bool,
|
||||||
|
|
|
@ -83,6 +83,13 @@
|
||||||
width: calc(100% + 1rem);
|
width: calc(100% + 1rem);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
content: "";
|
content: "";
|
||||||
|
|
||||||
|
/*
|
||||||
|
Because this :before is absolutely positioned, it will eat clicks
|
||||||
|
from non-absolute elements after it (like the author link).
|
||||||
|
Prevent this by explicitly disabling pointer events on this background element.
|
||||||
|
*/
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-top-row {
|
.comment-top-row {
|
||||||
|
|
|
@ -74,6 +74,7 @@ class TopLevelComment extends React.Component {
|
||||||
const {
|
const {
|
||||||
author,
|
author,
|
||||||
canDelete,
|
canDelete,
|
||||||
|
canDeleteWithoutConfirm,
|
||||||
canReply,
|
canReply,
|
||||||
canReport,
|
canReport,
|
||||||
canRestore,
|
canRestore,
|
||||||
|
@ -103,6 +104,7 @@ class TopLevelComment extends React.Component {
|
||||||
content,
|
content,
|
||||||
datetimeCreated,
|
datetimeCreated,
|
||||||
canDelete,
|
canDelete,
|
||||||
|
canDeleteWithoutConfirm,
|
||||||
canReply,
|
canReply,
|
||||||
canReport,
|
canReport,
|
||||||
canRestore,
|
canRestore,
|
||||||
|
@ -126,6 +128,7 @@ class TopLevelComment extends React.Component {
|
||||||
<Comment
|
<Comment
|
||||||
author={reply.author}
|
author={reply.author}
|
||||||
canDelete={canDelete}
|
canDelete={canDelete}
|
||||||
|
canDeleteWithoutConfirm={canDeleteWithoutConfirm}
|
||||||
canReply={canReply}
|
canReply={canReply}
|
||||||
canReport={canReport}
|
canReport={canReport}
|
||||||
canRestore={canRestore && parentVisible}
|
canRestore={canRestore && parentVisible}
|
||||||
|
@ -168,6 +171,7 @@ TopLevelComment.propTypes = {
|
||||||
username: PropTypes.string
|
username: PropTypes.string
|
||||||
}),
|
}),
|
||||||
canDelete: PropTypes.bool,
|
canDelete: PropTypes.bool,
|
||||||
|
canDeleteWithoutConfirm: PropTypes.bool,
|
||||||
canReply: PropTypes.bool,
|
canReply: PropTypes.bool,
|
||||||
canReport: PropTypes.bool,
|
canReport: PropTypes.bool,
|
||||||
canRestore: PropTypes.bool,
|
canRestore: PropTypes.bool,
|
||||||
|
@ -190,6 +194,7 @@ TopLevelComment.propTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
TopLevelComment.defaultProps = {
|
TopLevelComment.defaultProps = {
|
||||||
|
canDeleteWithoutConfirm: false,
|
||||||
defaultExpanded: false,
|
defaultExpanded: false,
|
||||||
moreRepliesToLoad: false
|
moreRepliesToLoad: false
|
||||||
};
|
};
|
||||||
|
|
|
@ -599,6 +599,7 @@ const PreviewPresentation = ({
|
||||||
<TopLevelComment
|
<TopLevelComment
|
||||||
author={comment.author}
|
author={comment.author}
|
||||||
canDelete={canDeleteComments}
|
canDelete={canDeleteComments}
|
||||||
|
canDeleteWithoutConfirm={isAdmin}
|
||||||
canReply={isLoggedIn && projectInfo.comments_allowed && isShared}
|
canReply={isLoggedIn && projectInfo.comments_allowed && isShared}
|
||||||
canReport={isLoggedIn}
|
canReport={isLoggedIn}
|
||||||
canRestore={canRestoreComments}
|
canRestore={canRestoreComments}
|
||||||
|
|
34
test/unit/components/info-button.test.jsx
Normal file
34
test/unit/components/info-button.test.jsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||||
|
import InfoButton from '../../../src/components/info-button/info-button';
|
||||||
|
|
||||||
|
describe('InfoButton', () => {
|
||||||
|
test('Info button defaults to not visible', () => {
|
||||||
|
const component = mountWithIntl(
|
||||||
|
<InfoButton
|
||||||
|
message="Here is some info about something!"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(component.find('div.info-button-message').exists()).toEqual(false);
|
||||||
|
});
|
||||||
|
test('clicking on info button makes info message visible', () => {
|
||||||
|
const component = mountWithIntl(
|
||||||
|
<InfoButton
|
||||||
|
message="Here is some info about something!"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
component.find('div.info-button').simulate('click');
|
||||||
|
expect(component.find('div.info-button-message').exists()).toEqual(true);
|
||||||
|
});
|
||||||
|
test('after message is visible, mouseOut makes it vanish', () => {
|
||||||
|
const component = mountWithIntl(
|
||||||
|
<InfoButton
|
||||||
|
message="Here is some info about something!"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
component.find('div.info-button').simulate('click');
|
||||||
|
expect(component.find('div.info-button-message').exists()).toEqual(true);
|
||||||
|
component.find('div.info-button').simulate('mouseOut');
|
||||||
|
expect(component.find('div.info-button-message').exists()).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
64
test/unit/components/join-flow-step.test.jsx
Normal file
64
test/unit/components/join-flow-step.test.jsx
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||||
|
import JoinFlowStep from '../../../src/components/join-flow/join-flow-step';
|
||||||
|
|
||||||
|
describe('JoinFlowStep', () => {
|
||||||
|
|
||||||
|
test('components exist when props present', () => {
|
||||||
|
const props = {
|
||||||
|
children: null,
|
||||||
|
className: 'join-flow-step-class',
|
||||||
|
description: 'description text',
|
||||||
|
headerImgSrc: '/image.png',
|
||||||
|
onSubmit: jest.fn(),
|
||||||
|
nextButton: 'some stuff',
|
||||||
|
title: 'join flow step title',
|
||||||
|
waiting: true
|
||||||
|
};
|
||||||
|
const component = mountWithIntl(
|
||||||
|
<JoinFlowStep
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(component.find('div.join-flow-header-image').exists()).toEqual(true);
|
||||||
|
expect(component.find({src: props.headerImgSrc}).exists()).toEqual(true);
|
||||||
|
expect(component.find('.join-flow-inner-content').exists()).toEqual(true);
|
||||||
|
expect(component.find('.join-flow-title').exists()).toEqual(true);
|
||||||
|
expect(component.find('.join-flow-title').first()
|
||||||
|
.prop('title')).toEqual(props.title);
|
||||||
|
expect(component.find('div.join-flow-description').exists()).toEqual(true);
|
||||||
|
expect(component.find('div.join-flow-description').text()).toEqual(props.description);
|
||||||
|
expect(component.find('NextStepButton').prop('waiting')).toEqual(true);
|
||||||
|
expect(component.find('NextStepButton').prop('content')).toEqual(props.nextButton);
|
||||||
|
|
||||||
|
component.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('components do not exist when props not present', () => {
|
||||||
|
const component = mountWithIntl(
|
||||||
|
<JoinFlowStep />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(component.find('div.join-flow-header-image').exists()).toEqual(false);
|
||||||
|
expect(component.find('.join-flow-inner-content').exists()).toEqual(true);
|
||||||
|
expect(component.find('.join-flow-title').exists()).toEqual(false);
|
||||||
|
expect(component.find('div.join-flow-description').exists()).toEqual(false);
|
||||||
|
expect(component.find('NextStepButton').prop('waiting')).toEqual(false);
|
||||||
|
|
||||||
|
component.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('clicking submit calls passed in function', () => {
|
||||||
|
const props = {
|
||||||
|
onSubmit: jest.fn()
|
||||||
|
};
|
||||||
|
const component = mountWithIntl(
|
||||||
|
<JoinFlowStep
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
component.find('button[type="submit"]').simulate('submit');
|
||||||
|
expect(props.onSubmit).toHaveBeenCalled();
|
||||||
|
component.unmount();
|
||||||
|
});
|
||||||
|
});
|
|
@ -11,17 +11,17 @@ describe('unit test lib/validate.js', () => {
|
||||||
response = validate.validateUsernameLocally('abc-def-ghi');
|
response = validate.validateUsernameLocally('abc-def-ghi');
|
||||||
expect(response).toEqual({valid: true});
|
expect(response).toEqual({valid: true});
|
||||||
response = validate.validateUsernameLocally('');
|
response = validate.validateUsernameLocally('');
|
||||||
expect(response).toEqual({valid: false, errMsgId: 'form.validationRequired'});
|
expect(response).toEqual({valid: false, errMsgId: 'general.required'});
|
||||||
response = validate.validateUsernameLocally('ab');
|
response = validate.validateUsernameLocally('ab');
|
||||||
expect(response).toEqual({valid: false, errMsgId: 'form.validationUsernameMinLength'});
|
expect(response).toEqual({valid: false, errMsgId: 'registration.validationUsernameMinLength'});
|
||||||
response = validate.validateUsernameLocally('abcdefghijklmnopqrstu');
|
response = validate.validateUsernameLocally('abcdefghijklmnopqrstu');
|
||||||
expect(response).toEqual({valid: false, errMsgId: 'form.validationUsernameMaxLength'});
|
expect(response).toEqual({valid: false, errMsgId: 'registration.validationUsernameMaxLength'});
|
||||||
response = validate.validateUsernameLocally('abc def');
|
response = validate.validateUsernameLocally('abc def');
|
||||||
expect(response).toEqual({valid: false, errMsgId: 'form.validationUsernameRegexp'});
|
expect(response).toEqual({valid: false, errMsgId: 'registration.validationUsernameRegexp'});
|
||||||
response = validate.validateUsernameLocally('abc!def');
|
response = validate.validateUsernameLocally('abc!def');
|
||||||
expect(response).toEqual({valid: false, errMsgId: 'form.validationUsernameRegexp'});
|
expect(response).toEqual({valid: false, errMsgId: 'registration.validationUsernameRegexp'});
|
||||||
response = validate.validateUsernameLocally('abc😄def');
|
response = validate.validateUsernameLocally('abc😄def');
|
||||||
expect(response).toEqual({valid: false, errMsgId: 'form.validationUsernameRegexp'});
|
expect(response).toEqual({valid: false, errMsgId: 'registration.validationUsernameRegexp'});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('validate password', () => {
|
test('validate password', () => {
|
||||||
|
@ -34,11 +34,15 @@ describe('unit test lib/validate.js', () => {
|
||||||
response = validate.validatePassword('passwo');
|
response = validate.validatePassword('passwo');
|
||||||
expect(response).toEqual({valid: true});
|
expect(response).toEqual({valid: true});
|
||||||
response = validate.validatePassword('');
|
response = validate.validatePassword('');
|
||||||
expect(response).toEqual({valid: false, errMsgId: 'form.validationRequired'});
|
expect(response).toEqual({valid: false, errMsgId: 'general.required'});
|
||||||
response = validate.validatePassword('abcde');
|
response = validate.validatePassword('abcde');
|
||||||
expect(response).toEqual({valid: false, errMsgId: 'registration.validationPasswordLength'});
|
expect(response).toEqual({valid: false, errMsgId: 'registration.validationPasswordLength'});
|
||||||
response = validate.validatePassword('password');
|
response = validate.validatePassword('password');
|
||||||
expect(response).toEqual({valid: false, errMsgId: 'registration.validationPasswordNotEquals'});
|
expect(response).toEqual({valid: false, errMsgId: 'registration.validationPasswordNotEquals'});
|
||||||
|
response = validate.validatePassword('abcdefg', 'abcdefg');
|
||||||
|
expect(response).toEqual({valid: false, errMsgId: 'registration.validationPasswordNotUsername'});
|
||||||
|
response = validate.validatePassword('abcdefg', 'abcdefG');
|
||||||
|
expect(response).toEqual({valid: true});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('validate password confirm', () => {
|
test('validate password confirm', () => {
|
||||||
|
@ -51,12 +55,12 @@ describe('unit test lib/validate.js', () => {
|
||||||
response = validate.validatePasswordConfirm('passwo', 'passwo');
|
response = validate.validatePasswordConfirm('passwo', 'passwo');
|
||||||
expect(response).toEqual({valid: true});
|
expect(response).toEqual({valid: true});
|
||||||
response = validate.validatePasswordConfirm('', '');
|
response = validate.validatePasswordConfirm('', '');
|
||||||
expect(response).toEqual({valid: false, errMsgId: 'form.validationRequired'});
|
expect(response).toEqual({valid: false, errMsgId: 'general.required'});
|
||||||
response = validate.validatePasswordConfirm('abcdef', 'abcdefg');
|
response = validate.validatePasswordConfirm('abcdef', 'abcdefg');
|
||||||
expect(response).toEqual({valid: false, errMsgId: 'general.error'});
|
expect(response).toEqual({valid: false, errMsgId: 'registration.validationPasswordConfirmNotEquals'});
|
||||||
response = validate.validatePasswordConfirm('abcdef', '123456');
|
response = validate.validatePasswordConfirm('abcdef', '123456');
|
||||||
expect(response).toEqual({valid: false, errMsgId: 'general.error'});
|
expect(response).toEqual({valid: false, errMsgId: 'registration.validationPasswordConfirmNotEquals'});
|
||||||
response = validate.validatePasswordConfirm('', 'abcdefg');
|
response = validate.validatePasswordConfirm('', 'abcdefg');
|
||||||
expect(response).toEqual({valid: false, errMsgId: 'general.error'});
|
expect(response).toEqual({valid: false, errMsgId: 'registration.validationPasswordConfirmNotEquals'});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue