diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..520b0adb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,21 @@ +name: Test + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + node-version: ['14.x', '16.x', '18.x'] + + steps: + - uses: actions/checkout@v4 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - run: npm run build:ci + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index fae234b9..00000000 --- a/.travis.yml +++ /dev/null @@ -1,31 +0,0 @@ -language: node_js -notifications: - email: - recipients: - - bugs@unchained-capital.com - on_success: change - on_failure: always - -jobs: - include: - - stage: "Tests" - node_js: 16 - name: "Node JS 16" - script: npm run test - - node_js: 16 - name: "Node JS 16" - script: npm run ci - - stage: build - node_js: 16 - script: npm run build:ci - before_deploy: - - git remote set-url origin https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git - - git config --global user.email "Travis CI" - - git config --global user.name "TravisCI" - deploy: - provider: script - script: "npm run deploy" - skip_cleanup: true - on: - tags: true - branch: master diff --git a/package-lock.json b/package-lock.json index b424541f..d317184d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,9 +72,12 @@ "commander": "^5.0.0", "commitlint-config-cz": "^0.13.1", "cz-customizable": "^6.2.0", + "eslint": "^7.32.0", "eslint-config-airbnb": "^18.1.0", "eslint-config-prettier": "^6.10.1", + "eslint-plugin-import": "^2.27.5", "eslint-plugin-jest-dom": "^3.0.1", + "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-react": "^7.19.0", "eslint-plugin-testing-library": "^3.1.0", @@ -93,6 +96,15 @@ "node": ">=16" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@adobe/css-tools": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz", @@ -2849,7 +2861,6 @@ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", "dev": true, - "peer": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.1.1", @@ -2866,11 +2877,10 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, - "peer": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -2886,7 +2896,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -2899,7 +2908,6 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", "dev": true, - "peer": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.0", "debug": "^4.1.1", @@ -2913,8 +2921,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@hutson/parse-repository-url": { "version": "3.0.2", @@ -4426,8 +4433,7 @@ "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/lodash": { "version": "4.14.191", @@ -4710,11 +4716,10 @@ "dev": true }, "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true, - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4732,24 +4737,11 @@ "acorn-walk": "^8.0.2" } }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "peer": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -4785,7 +4777,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4802,7 +4793,6 @@ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -4989,7 +4979,6 @@ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -5082,15 +5071,13 @@ "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", - "dev": true, - "peer": true + "dev": true }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -5163,7 +5150,6 @@ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.3.tgz", "integrity": "sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -5181,7 +5167,6 @@ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", "dev": true, - "peer": true, "dependencies": { "deep-equal": "^2.0.5" } @@ -6908,8 +6893,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/dargs": { "version": "7.0.0", @@ -7226,7 +7210,6 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, - "peer": true, "dependencies": { "esutils": "^2.0.2" }, @@ -7406,8 +7389,7 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/end-of-stream": { "version": "1.4.4", @@ -7418,13 +7400,13 @@ } }, "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, - "peer": true, "dependencies": { - "ansi-colors": "^4.1.1" + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8.6" @@ -7721,7 +7703,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, - "peer": true, "dependencies": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", @@ -7795,7 +7776,7 @@ "eslint-plugin-react-hooks": "^4 || ^3 || ^2.3.0 || ^1.7.0" } }, - "node_modules/eslint-config-airbnb-base": { + "node_modules/eslint-config-airbnb/node_modules/eslint-config-airbnb-base": { "version": "14.2.1", "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz", "integrity": "sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA==", @@ -7842,7 +7823,6 @@ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", "dev": true, - "peer": true, "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.11.0", @@ -7854,7 +7834,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "peer": true, "dependencies": { "ms": "^2.1.1" } @@ -7864,7 +7843,6 @@ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", "dev": true, - "peer": true, "dependencies": { "debug": "^3.2.7" }, @@ -7882,7 +7860,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "peer": true, "dependencies": { "ms": "^2.1.1" } @@ -7892,7 +7869,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", "dev": true, - "peer": true, "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", @@ -7922,7 +7898,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "peer": true, "dependencies": { "ms": "^2.1.1" } @@ -7932,7 +7907,6 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, - "peer": true, "dependencies": { "esutils": "^2.0.2" }, @@ -7945,7 +7919,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -7974,7 +7947,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz", "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", "dev": true, - "peer": true, "dependencies": { "@babel/runtime": "^7.20.7", "aria-query": "^5.1.3", @@ -8005,7 +7977,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -8178,7 +8149,6 @@ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "dev": true, - "peer": true, "dependencies": { "@babel/highlight": "^7.10.4" } @@ -8188,7 +8158,6 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true, - "peer": true, "engines": { "node": ">=10" } @@ -8198,7 +8167,6 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, - "peer": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -8214,7 +8182,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -8227,7 +8194,6 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, - "peer": true, "dependencies": { "acorn": "^7.4.0", "acorn-jsx": "^5.3.1", @@ -8237,6 +8203,18 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/espree/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -8255,7 +8233,6 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, - "peer": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -8383,8 +8360,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "peer": true + "dev": true }, "node_modules/fast-diff": { "version": "1.2.0", @@ -8444,7 +8420,6 @@ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, - "peer": true, "dependencies": { "flat-cache": "^3.0.4" }, @@ -8566,7 +8541,6 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, - "peer": true, "dependencies": { "flatted": "^3.1.0", "rimraf": "^3.0.2" @@ -8579,8 +8553,7 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/flux-standard-action": { "version": "2.1.2", @@ -8697,8 +8670,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true, - "peer": true + "dev": true }, "node_modules/functions-have-names": { "version": "1.2.3", @@ -9672,7 +9644,6 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true, - "peer": true, "engines": { "node": ">= 4" } @@ -11625,18 +11596,6 @@ } } }, - "node_modules/jsdom/node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/jsdom/node_modules/tr46": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", @@ -11718,8 +11677,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/json-stable-stringify": { "version": "1.0.2", @@ -11736,8 +11694,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/json-stringify-safe": { "version": "5.0.1", @@ -11950,15 +11907,13 @@ "version": "0.3.22", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/language-tags": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", "dev": true, - "peer": true, "dependencies": { "language-subtag-registry": "~0.3.2" } @@ -12086,7 +12041,6 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, - "peer": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -12186,15 +12140,13 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/long": { "version": "4.0.0", @@ -13109,18 +13061,17 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, - "peer": true, "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -13485,7 +13436,6 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, - "peer": true, "engines": { "node": ">= 0.8.0" } @@ -13580,7 +13530,6 @@ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true, - "peer": true, "engines": { "node": ">=0.4.0" } @@ -14329,7 +14278,6 @@ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true, - "peer": true, "engines": { "node": ">=8" }, @@ -14389,7 +14337,6 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -14531,7 +14478,6 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, - "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -14907,7 +14853,6 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -14925,7 +14870,6 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -15660,7 +15604,6 @@ "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", "dev": true, - "peer": true, "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", @@ -15677,7 +15620,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -15693,15 +15635,13 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "peer": true + "dev": true }, "node_modules/table/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -15710,15 +15650,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "peer": true + "dev": true }, "node_modules/table/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -15827,8 +15765,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/through": { "version": "2.3.8", @@ -15997,7 +15934,6 @@ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", "dev": true, - "peer": true, "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -16010,7 +15946,6 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, - "peer": true, "dependencies": { "minimist": "^1.2.0" }, @@ -16060,7 +15995,6 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, - "peer": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -16344,7 +16278,6 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "peer": true, "dependencies": { "punycode": "^2.1.0" } @@ -16451,11 +16384,10 @@ } }, "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true, - "peer": true + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", + "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", + "dev": true }, "node_modules/v8-to-istanbul": { "version": "9.1.0", @@ -16983,6 +16915,12 @@ } }, "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true + }, "@adobe/css-tools": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz", @@ -18861,7 +18799,6 @@ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", "dev": true, - "peer": true, "requires": { "ajv": "^6.12.4", "debug": "^4.1.1", @@ -18875,11 +18812,10 @@ }, "dependencies": { "globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, - "peer": true, "requires": { "type-fest": "^0.20.2" } @@ -18888,8 +18824,7 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "peer": true + "dev": true } } }, @@ -18898,7 +18833,6 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", "dev": true, - "peer": true, "requires": { "@humanwhocodes/object-schema": "^1.2.0", "debug": "^4.1.1", @@ -18909,8 +18843,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true, - "peer": true + "dev": true }, "@hutson/parse-repository-url": { "version": "3.0.2", @@ -20070,8 +20003,7 @@ "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "peer": true + "dev": true }, "@types/lodash": { "version": "4.14.191", @@ -20304,11 +20236,10 @@ "dev": true }, "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "peer": true + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true }, "acorn-globals": { "version": "7.0.1", @@ -20318,14 +20249,6 @@ "requires": { "acorn": "^8.1.0", "acorn-walk": "^8.0.2" - }, - "dependencies": { - "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true - } } }, "acorn-jsx": { @@ -20333,7 +20256,6 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "peer": true, "requires": {} }, "acorn-walk": { @@ -20361,7 +20283,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -20373,8 +20294,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "peer": true + "dev": true }, "ansi-escapes": { "version": "3.2.0", @@ -20527,7 +20447,6 @@ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -20607,15 +20526,13 @@ "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", - "dev": true, - "peer": true + "dev": true }, "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "peer": true + "dev": true }, "async": { "version": "2.6.4", @@ -20666,8 +20583,7 @@ "version": "4.6.3", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.3.tgz", "integrity": "sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg==", - "dev": true, - "peer": true + "dev": true }, "axios": { "version": "0.21.4", @@ -20682,7 +20598,6 @@ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", "dev": true, - "peer": true, "requires": { "deep-equal": "^2.0.5" } @@ -22084,8 +21999,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true, - "peer": true + "dev": true }, "dargs": { "version": "7.0.0", @@ -22319,7 +22233,6 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, - "peer": true, "requires": { "esutils": "^2.0.2" } @@ -22464,8 +22377,7 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "peer": true + "dev": true }, "end-of-stream": { "version": "1.4.4", @@ -22476,13 +22388,13 @@ } }, "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, - "peer": true, "requires": { - "ansi-colors": "^4.1.1" + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" } }, "entities": { @@ -22707,7 +22619,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, - "peer": true, "requires": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", @@ -22756,7 +22667,6 @@ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "dev": true, - "peer": true, "requires": { "@babel/highlight": "^7.10.4" } @@ -22765,15 +22675,13 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "peer": true + "dev": true }, "globals": { "version": "13.20.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, - "peer": true, "requires": { "type-fest": "^0.20.2" } @@ -22782,8 +22690,7 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "peer": true + "dev": true } } }, @@ -22796,17 +22703,19 @@ "eslint-config-airbnb-base": "^14.2.1", "object.assign": "^4.1.2", "object.entries": "^1.1.2" - } - }, - "eslint-config-airbnb-base": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz", - "integrity": "sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA==", - "dev": true, - "requires": { - "confusing-browser-globals": "^1.0.10", - "object.assign": "^4.1.2", - "object.entries": "^1.1.2" + }, + "dependencies": { + "eslint-config-airbnb-base": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz", + "integrity": "sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.2" + } + } } }, "eslint-config-prettier": { @@ -22831,7 +22740,6 @@ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", "dev": true, - "peer": true, "requires": { "debug": "^3.2.7", "is-core-module": "^2.11.0", @@ -22843,7 +22751,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "peer": true, "requires": { "ms": "^2.1.1" } @@ -22855,7 +22762,6 @@ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", "dev": true, - "peer": true, "requires": { "debug": "^3.2.7" }, @@ -22865,7 +22771,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "peer": true, "requires": { "ms": "^2.1.1" } @@ -22877,7 +22782,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", "dev": true, - "peer": true, "requires": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", @@ -22901,7 +22805,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "peer": true, "requires": { "ms": "^2.1.1" } @@ -22911,7 +22814,6 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, - "peer": true, "requires": { "esutils": "^2.0.2" } @@ -22920,8 +22822,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "peer": true + "dev": true } } }, @@ -22941,7 +22842,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz", "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", "dev": true, - "peer": true, "requires": { "@babel/runtime": "^7.20.7", "aria-query": "^5.1.3", @@ -22965,8 +22865,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "peer": true + "dev": true } } }, @@ -23085,11 +22984,18 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, - "peer": true, "requires": { "acorn": "^7.4.0", "acorn-jsx": "^5.3.1", "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } } }, "esprima": { @@ -23103,7 +23009,6 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, - "peer": true, "requires": { "estraverse": "^5.1.0" } @@ -23201,8 +23106,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "peer": true + "dev": true }, "fast-diff": { "version": "1.2.0", @@ -23258,7 +23162,6 @@ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, - "peer": true, "requires": { "flat-cache": "^3.0.4" } @@ -23347,7 +23250,6 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, - "peer": true, "requires": { "flatted": "^3.1.0", "rimraf": "^3.0.2" @@ -23357,8 +23259,7 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true, - "peer": true + "dev": true }, "flux-standard-action": { "version": "2.1.2", @@ -23442,8 +23343,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true, - "peer": true + "dev": true }, "functions-have-names": { "version": "1.2.3", @@ -24179,8 +24079,7 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "peer": true + "dev": true }, "immutable": { "version": "4.3.0", @@ -25612,12 +25511,6 @@ "xml-name-validator": "^4.0.0" }, "dependencies": { - "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true - }, "tr46": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", @@ -25672,8 +25565,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "peer": true + "dev": true }, "json-stable-stringify": { "version": "1.0.2", @@ -25687,8 +25579,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "peer": true + "dev": true }, "json-stringify-safe": { "version": "5.0.1", @@ -25861,15 +25752,13 @@ "version": "0.3.22", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", - "dev": true, - "peer": true + "dev": true }, "language-tags": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", "dev": true, - "peer": true, "requires": { "language-subtag-registry": "~0.3.2" } @@ -25978,7 +25867,6 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, - "peer": true, "requires": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -26065,15 +25953,13 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "peer": true + "dev": true }, "lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true, - "peer": true + "dev": true }, "long": { "version": "4.0.0", @@ -26739,18 +26625,17 @@ "dev": true }, "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, - "peer": true, "requires": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" } }, "os-browserify": { @@ -27018,8 +26903,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "peer": true + "dev": true }, "prettier": { "version": "2.8.4", @@ -27091,8 +26975,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "peer": true + "dev": true }, "prompts": { "version": "2.4.2", @@ -27697,8 +27580,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "peer": true + "dev": true }, "regexpu-core": { "version": "5.3.2", @@ -27741,8 +27623,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "peer": true + "dev": true }, "require-main-filename": { "version": "2.0.0", @@ -27850,7 +27731,6 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, - "peer": true, "requires": { "glob": "^7.1.3" } @@ -28129,7 +28009,6 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, - "peer": true, "requires": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -28140,8 +28019,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "peer": true + "dev": true } } }, @@ -28710,7 +28588,6 @@ "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", "dev": true, - "peer": true, "requires": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", @@ -28724,7 +28601,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, - "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -28736,29 +28612,25 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "peer": true + "dev": true }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "peer": true + "dev": true }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "peer": true + "dev": true }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "peer": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -28850,8 +28722,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "peer": true + "dev": true }, "through": { "version": "2.3.8", @@ -28990,7 +28861,6 @@ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", "dev": true, - "peer": true, "requires": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -29003,7 +28873,6 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, - "peer": true, "requires": { "minimist": "^1.2.0" } @@ -29043,7 +28912,6 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, - "peer": true, "requires": { "prelude-ls": "^1.2.1" } @@ -29247,7 +29115,6 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "peer": true, "requires": { "punycode": "^2.1.0" } @@ -29347,11 +29214,10 @@ "dev": true }, "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true, - "peer": true + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", + "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", + "dev": true }, "v8-to-istanbul": { "version": "9.1.0", diff --git a/package.json b/package.json index b0e64d51..9bf3f753 100644 --- a/package.json +++ b/package.json @@ -37,9 +37,12 @@ "commander": "^5.0.0", "commitlint-config-cz": "^0.13.1", "cz-customizable": "^6.2.0", + "eslint": "^7.32.0", "eslint-config-airbnb": "^18.1.0", "eslint-config-prettier": "^6.10.1", + "eslint-plugin-import": "^2.27.5", "eslint-plugin-jest-dom": "^3.0.1", + "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-react": "^7.19.0", "eslint-plugin-testing-library": "^3.1.0", @@ -58,12 +61,11 @@ "build": "npm run check && VITE_GIT_SHA=`git rev-parse --short HEAD` vite build", "build:ci": "npm run ci && VITE_GIT_SHA=`git rev-parse --short HEAD` vite build", "check": "npm run lint && npm run test:ci", - "ci": "npm run lint && npm run commitlint-travis && npm run test:ci", + "ci": "npm run lint && npm run test:ci", "commit": "./node_modules/cz-customizable/standalone.js", "deploy": "gh-pages -d build", "gh-pages": "CI=true npm run build && gh-pages -d build", "lint": "eslint --ext .js,.jsx src/", - "commitlint-travis": "commitlint-travis", "predeploy": "npm run build", "release": "standard-version", "start": "HTTPS=true npm run preview", diff --git a/src/components/AddressTypePicker.jsx b/src/components/AddressTypePicker.jsx index dc782274..0a4740ce 100644 --- a/src/components/AddressTypePicker.jsx +++ b/src/components/AddressTypePicker.jsx @@ -17,66 +17,62 @@ import { // Actions import { setAddressType } from "../actions/settingsActions"; -class AddressTypePicker extends React.Component { - handleTypeChange = (event) => { - const { setType } = this.props; +const AddressTypePicker = ({ setType, addressType, frozen }) => { + const handleTypeChange = (event) => { setType(event.target.value); }; - render() { - const { addressType, frozen } = this.props; - return ( - - - - - - } - name="type" - value={P2SH} - label={P2SH} - onChange={this.handleTypeChange} - checked={addressType === P2SH} - disabled={frozen} - /> - } - name="type" - value={P2SH_P2WSH} - label={P2SH_P2WSH} - onChange={this.handleTypeChange} - checked={addressType === P2SH_P2WSH} - disabled={frozen} - /> - } - name="type" - value={P2WSH} - label={P2WSH} - onChange={this.handleTypeChange} - checked={addressType === P2WSH} - disabled={frozen} - /> - - - - Choose ' - {P2WSH} - ' for best practices, ' - {P2SH} - ' for greatest compatibility. - - - - - - ); - } -} + return ( + + + + + + } + name="type" + value={P2SH} + label={P2SH} + onChange={handleTypeChange} + checked={addressType === P2SH} + disabled={frozen} + /> + } + name="type" + value={P2SH_P2WSH} + label={P2SH_P2WSH} + onChange={handleTypeChange} + checked={addressType === P2SH_P2WSH} + disabled={frozen} + /> + } + name="type" + value={P2WSH} + label={P2WSH} + onChange={handleTypeChange} + checked={addressType === P2WSH} + disabled={frozen} + /> + + + + Choose ' + {P2WSH} + ' for best practices, ' + {P2SH} + ' for greatest compatibility. + + + + + + ); +}; AddressTypePicker.propTypes = { addressType: PropTypes.string.isRequired, diff --git a/src/components/AppContainer.jsx b/src/components/AppContainer.jsx index 68a61bc7..a43c9597 100644 --- a/src/components/AppContainer.jsx +++ b/src/components/AppContainer.jsx @@ -1,31 +1,28 @@ -import React, { Component } from "react"; +import React, { useEffect } from "react"; import PropTypes from "prop-types"; import { createBrowserHistory } from "history"; import { connect } from "react-redux"; import App from "./App"; -class AppContainer extends Component { - history = createBrowserHistory(); +const AppContainer = ({ resetApp }) => { + const history = createBrowserHistory(); - componentDidMount() { - const { resetApp } = this.props; + useEffect(() => { // Listen for changes to the current location // and reset the redux store which is needed // to avoid conflicts in the state between views/pages - this.unlisten = this.history.listen(() => { + const unlisten = history.listen(() => { resetApp(); }); - } - componentWillUnmount() { - this.unlisten(); - } + return () => { + unlisten(); + }; + }, []); - render() { - return ; - } -} + return ; +}; AppContainer.propTypes = { resetApp: PropTypes.func.isRequired, diff --git a/src/components/ClientPicker/index.jsx b/src/components/ClientPicker/index.jsx index 1b7d1c55..ccc11eda 100644 --- a/src/components/ClientPicker/index.jsx +++ b/src/components/ClientPicker/index.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState } from "react"; import PropTypes from "prop-types"; import { connect } from "react-redux"; import { @@ -30,149 +30,142 @@ import { import PrivateClientSettings from "./PrivateClientSettings"; -class ClientPicker extends React.Component { - static validatePassword() { +const ClientPicker = ({ + setType, + network, + setUrl, + setUrlError, + setUsername, + setUsernameError, + setPassword, + setPasswordError, + client, + onSuccess, + urlError, + usernameError, + passwordError, + privateNotes, +}) => { + const [urlEdited, setUrlEdited] = useState(false); + const [connectError, setConnectError] = useState(""); + const [connectSuccess, setConnectSuccess] = useState(false); + + const validatePassword = () => { return ""; - } + }; - static validateUsername() { + const validateUsername = () => { return ""; - } + }; - static validateUrl(host) { + const validateUrl = (host) => { const validhost = /^http(s)?:\/\/[^\s]+$/.exec(host); if (!validhost) return "Must be a valid URL."; return ""; - } - - constructor(props) { - super(props); - - this.state = { - urlEdited: false, - connectError: "", - connectSuccess: false, - }; - } - - handleTypeChange = (event) => { - const { setType, network, setUrl } = this.props; - const { urlEdited } = this.state; + }; - const type = event.target.value; - if (type === "private" && !urlEdited) { + const handleTypeChange = (event) => { + const clientType = event.target.value; + if (clientType === "private" && !urlEdited) { setUrl(`http://localhost:${network === "mainnet" ? 8332 : 18332}`); } - setType(type); + setType(clientType); }; - handleUrlChange = (event) => { - const { setUrl, setUrlError } = this.props; - const { urlEdited } = this.state; + const handleUrlChange = (event) => { const url = event.target.value; - const error = ClientPicker.validateUrl(url); - if (!urlEdited && !error) this.setState({ urlEdited: true }); + const error = validateUrl(url); + if (!urlEdited && !error) setUrlEdited(true); setUrl(url); setUrlError(error); }; - handleUsernameChange = (event) => { - const { setUsername, setUsernameError } = this.props; + const handleUsernameChange = (event) => { const username = event.target.value; - const error = ClientPicker.validateUsername(username); + const error = validateUsername(username); setUsername(username); setUsernameError(error); }; - handlePasswordChange = (event) => { - const { setPassword, setPasswordError } = this.props; + const handlePasswordChange = (event) => { const password = event.target.value; - const error = ClientPicker.validatePassword(password); + const error = validatePassword(password); setPassword(password); setPasswordError(error); }; - testConnection = async () => { - const { network, client, onSuccess } = this.props; - this.setState({ connectError: "", connectSuccess: false }); + const testConnection = async () => { + setConnectError(""); + setConnectSuccess(false); try { await fetchFeeEstimate(network, client); if (onSuccess) { onSuccess(); } - this.setState({ connectSuccess: true }); + setConnectSuccess(true); } catch (e) { - this.setState({ connectError: e.message }); + setConnectError(e.message); } }; - render() { - const { client, urlError, usernameError, passwordError, privateNotes } = - this.props; - const { connectSuccess, connectError } = this.state; - return ( - - - + return ( + + + + + + + + + } + name="clientType" + value="public" + label={Public} + onChange={handleTypeChange} + checked={client.type === "public"} + /> + } + name="clientType" + value="private" + label="Private" + onChange={handleTypeChange} + checked={client.type === "private"} + /> + + {client.type === "public" && ( + + {"'Public' uses the "} + mempool.space + {" API. Switch to private to use a "} + bitcoind + {" node."} + + )} + {client.type === "private" && ( + handleUrlChange(event)} + handleUsernameChange={(event) => handleUsernameChange(event)} + handlePasswordChange={(event) => handlePasswordChange(event)} + client={client} + urlError={urlError} + usernameError={usernameError} + passwordError={passwordError} + privateNotes={privateNotes} + connectSuccess={connectSuccess} + connectError={connectError} + testConnection={() => testConnection()} + /> + )} + - - - - - } - name="clientType" - value="public" - label={Public} - onChange={this.handleTypeChange} - checked={client.type === "public"} - /> - } - name="clientType" - value="private" - label="Private" - onChange={this.handleTypeChange} - checked={client.type === "private"} - /> - - {client.type === "public" && ( - - {"'Public' uses the "} - mempool.space - {" API. Switch to private to use a "} - bitcoind - {" node."} - - )} - {client.type === "private" && ( - this.handleUrlChange(event)} - handleUsernameChange={(event) => - this.handleUsernameChange(event) - } - handlePasswordChange={(event) => - this.handlePasswordChange(event) - } - client={client} - urlError={urlError} - usernameError={usernameError} - passwordError={passwordError} - privateNotes={privateNotes} - connectSuccess={connectSuccess} - connectError={connectError} - testConnection={() => this.testConnection()} - /> - )} - - - - - ); - } -} + + + ); +}; ClientPicker.propTypes = { client: PropTypes.shape({ diff --git a/src/components/Coldcard/ColdcardExtendedPublicKeyImporter.jsx b/src/components/Coldcard/ColdcardExtendedPublicKeyImporter.jsx index ddd551c3..c7aadee7 100644 --- a/src/components/Coldcard/ColdcardExtendedPublicKeyImporter.jsx +++ b/src/components/Coldcard/ColdcardExtendedPublicKeyImporter.jsx @@ -1,101 +1,81 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import PropTypes from "prop-types"; import { COLDCARD } from "unchained-wallets"; -import { Box, FormGroup, FormHelperText } from "@mui/material"; +import { Box, FormGroup } from "@mui/material"; import { MAINNET, P2SH } from "unchained-bitcoin"; import { ColdcardJSONReader } from "."; import IndirectExtendedPublicKeyImporter from "../Wallet/IndirectExtendedPublicKeyImporter"; -class ColdcardExtendedPublicKeyImporter extends React.Component { - constructor(props) { - super(props); - const coldcardBIP32Path = this.getColdcardBip32Path(); - this.state = { - COLDCARD_MULTISIG_BIP32_PATH: coldcardBIP32Path, - }; - } - - componentDidMount = () => { - const { extendedPublicKeyImporter, validateAndSetBIP32Path } = this.props; - const { COLDCARD_MULTISIG_BIP32_PATH } = this.state; - if (extendedPublicKeyImporter.method === COLDCARD) { - validateAndSetBIP32Path( - COLDCARD_MULTISIG_BIP32_PATH, - () => {}, - () => {}, - {} - ); - } - }; - - componentDidUpdate(prevProps) { - const { validateAndSetBIP32Path, network, addressType } = this.props; - const coldcardBIP32Path = this.getColdcardBip32Path(); - - // Any updates to the network/addressType we should set the BIP32Path - if ( - prevProps.network !== network || - prevProps.addressType !== addressType - ) { - // eslint-disable-next-line react/no-did-update-set-state - this.setState({ COLDCARD_MULTISIG_BIP32_PATH: coldcardBIP32Path }); - validateAndSetBIP32Path( - coldcardBIP32Path, - () => {}, - () => {} - ); - } - } - +const ColdcardExtendedPublicKeyImporter = ({ + extendedPublicKeyImporter, + validateAndSetExtendedPublicKey, + validateAndSetBIP32Path, + validateAndSetRootFingerprint, + addressType, + network, + defaultBIP32Path, +}) => { // Unfortunately not possible to use our Multisig P2SH ROOT on a Coldcard atm // because they do not allow us to export m/45'/{0-1}'/0' yet. - getColdcardBip32Path = () => { - const { network, addressType, defaultBIP32Path } = this.props; + const getColdcardBip32Path = () => { const coinPath = network === MAINNET ? "0" : "1"; const coldcardP2SHPath = `m/45'/${coinPath}/0`; return addressType === P2SH ? coldcardP2SHPath : defaultBIP32Path; }; - render = () => { - const { - extendedPublicKeyImporter, - validateAndSetExtendedPublicKey, - validateAndSetBIP32Path, - validateAndSetRootFingerprint, - addressType, - network, - } = this.props; - const { extendedPublicKeyError, COLDCARD_MULTISIG_BIP32_PATH } = this.state; - return ( - - - - {extendedPublicKeyError} - - + const [coldcardMultisigBIP32Path, setColdcardMultisigBIP32Path] = useState( + getColdcardBip32Path() + ); + + const resetColdcardBIP32Path = () => { + validateAndSetBIP32Path( + coldcardMultisigBIP32Path, + () => {}, + () => {} ); }; - resetColdcardBIP32Path = () => { - const { validateAndSetBIP32Path } = this.props; - const { COLDCARD_MULTISIG_BIP32_PATH } = this.state; + useEffect(() => { + if (extendedPublicKeyImporter.method === COLDCARD) { + validateAndSetBIP32Path( + coldcardMultisigBIP32Path, + () => {}, + () => {}, + {} + ); + } + }, []); + + useEffect(() => { + const newColdcardBIP32Path = getColdcardBip32Path(); + // eslint-disable-next-line react/no-did-update-set-state + setColdcardMultisigBIP32Path(newColdcardBIP32Path); validateAndSetBIP32Path( - COLDCARD_MULTISIG_BIP32_PATH, + newColdcardBIP32Path, () => {}, () => {} ); - }; -} + // Any updates to the network/addressType we should set the BIP32Path + }, [network, addressType]); + + return ( + + + + + + ); +}; ColdcardExtendedPublicKeyImporter.propTypes = { extendedPublicKeyImporter: PropTypes.shape({ diff --git a/src/components/Coldcard/ColdcardFileReader.jsx b/src/components/Coldcard/ColdcardFileReader.jsx index e5432b9f..aa492b0a 100644 --- a/src/components/Coldcard/ColdcardFileReader.jsx +++ b/src/components/Coldcard/ColdcardFileReader.jsx @@ -1,5 +1,5 @@ // eslint-disable-next-line max-classes-per-file -import React, { Component } from "react"; +import React from "react"; import PropTypes from "prop-types"; import Dropzone from "react-dropzone"; import { Buffer } from "buffer/"; @@ -8,90 +8,28 @@ import { CloudUpload as UploadIcon } from "@mui/icons-material"; import { PSBT_MAGIC_HEX } from "unchained-bitcoin"; import styles from "./ColdcardFileReader.module.scss"; -class ColdcardFileReaderBase extends Component { - constructor(props) { - super(props); - this.state = { - fileType: props.fileType || "JSON", - }; - } - - render = () => { - const { - maxFileSize, - validFileFormats, - extendedPublicKeyImporter, - handleBIP32PathChange, - resetBIP32Path, - bip32PathIsDefault, - hasError, - errorMessage, - isTest, - } = this.props; - const { fileType } = this.state; - return ( - - {fileType === "JSON" && !isTest && ( - - - - - - {!bip32PathIsDefault() && ( - - )} - - - Use the default value if you don’t understand BIP32 paths. - - - )} -

- When you are ready, upload the {fileType} file from your Coldcard: -

- - - -

- {fileType === "JSON" ? "Upload The XPUB" : "Upload Signed PSBT"} -

-
-
-
- ); - }; - - singleAcceptedFile = (acceptedFiles, rejectedFiles) => { +const ColdcardFileReaderBase = ({ + maxFileSize, + validFileFormats, + extendedPublicKeyImporter, + handleBIP32PathChange, + resetBIP32Path, + bip32PathIsDefault, + hasError, + errorMessage, + isTest, + onReceive, + onReceivePSBT, + setError, + fileType = "JSON", +}) => { + const singleAcceptedFile = (acceptedFiles, rejectedFiles) => { return rejectedFiles.length === 0 && acceptedFiles.length === 1; }; - onDrop = async (acceptedFiles, rejectedFiles) => { - const { onReceive, onReceivePSBT, setError, hasError } = this.props; - const { fileType } = this.state; + const onDrop = async (acceptedFiles, rejectedFiles) => { if (hasError) return; // do not continue if the bip32path is invalid - if (this.singleAcceptedFile(acceptedFiles, rejectedFiles)) { + if (singleAcceptedFile(acceptedFiles, rejectedFiles)) { const file = acceptedFiles[0]; if (fileType === "JSON") { onReceive(await file.text()); @@ -120,7 +58,58 @@ class ColdcardFileReaderBase extends Component { setError(`This dropzone only accepts a single file.`); } }; -} + + return ( + + {fileType === "JSON" && !isTest && ( + + + + + + {!bip32PathIsDefault() && ( + + )} + + + Use the default value if you don’t understand BIP32 paths. + + + )} +

When you are ready, upload the {fileType} file from your Coldcard:

+ + + +

+ {fileType === "JSON" ? "Upload The XPUB" : "Upload Signed PSBT"} +

+
+
+
+ ); +}; ColdcardFileReaderBase.propTypes = { onReceive: PropTypes.func, @@ -147,7 +136,7 @@ ColdcardFileReaderBase.defaultProps = { resetBIP32Path: null, bip32PathIsDefault: null, errorMessage: "", - hasError: PropTypes.bool, + hasError: false, maxFileSize: 1048576, // 1MB fileType: "JSON", validFileFormats: ".json", @@ -155,14 +144,22 @@ ColdcardFileReaderBase.defaultProps = { isTest: false, }; -export class ColdcardJSONReader extends ColdcardFileReaderBase {} -ColdcardJSONReader.defaultProps = { - fileType: "JSON", - validFileFormats: ".json", +export const ColdcardJSONReader = (props) => { + return ( + + ); }; -export class ColdcardPSBTReader extends ColdcardFileReaderBase {} -ColdcardPSBTReader.defaultProps = { - fileType: "PSBT", - validFileFormats: ".psbt", +export const ColdcardPSBTReader = (props) => { + return ( + + ); }; diff --git a/src/components/Coldcard/ColdcardSignatureImporter.jsx b/src/components/Coldcard/ColdcardSignatureImporter.jsx index e3b6d7e4..cce18488 100644 --- a/src/components/Coldcard/ColdcardSignatureImporter.jsx +++ b/src/components/Coldcard/ColdcardSignatureImporter.jsx @@ -3,17 +3,16 @@ import PropTypes from "prop-types"; import IndirectSignatureImporter from "../ScriptExplorer/IndirectSignatureImporter"; import ColdcardSigner from "./ColdcardSigner"; -const ColdcardSignatureImporter = (props) => { - const { - signatureImporter, - extendedPublicKeyImporter, - inputs, - outputs, - inputsTotalSats, - fee, - validateAndSetSignature, - network, - } = props; +const ColdcardSignatureImporter = ({ + signatureImporter, + extendedPublicKeyImporter, + inputs, + outputs, + inputsTotalSats, + fee, + validateAndSetSignature, + network, +}) => { return ( { - const { walletName, interaction, setActive } = this.props; - const body = interaction.request().toBase64(); - const timestamp = moment().format("HHmm"); - const filename = `${timestamp}-${walletName}.psbt`; - downloadFile(body, filename); - setActive(); - }; - - handleWalletConfigDownloadClick = () => { - const { walletDetailsText } = this.props; - this.reshapeConfig(walletDetailsText); - }; - +const ColdcardSigner = ({ + onReceivePSBT, + setError, + walletDetailsText, + walletName, + interaction, + setActive, +}) => { // This tries to reshape it to a Coldcard Wallet Config via unchained-wallets - reshapeConfig = (walletDetails) => { + const reshapeConfig = (walletDetails) => { const walletConfig = JSON.parse(walletDetails); const { startingAddressIndex } = walletConfig; // If this is a config that's been rekeyed, note that in the name. @@ -40,28 +33,36 @@ class ColdcardSigner extends Component { ? "P2WSH-P2SH" : walletConfig.addressType; - const interaction = ConfigAdapter({ + const interactionAdapter = ConfigAdapter({ KEYSTORE: COLDCARD, jsonConfig: walletConfig, }); - const body = interaction.adapt(); + const body = interactionAdapter.adapt(); const filename = `wc-${walletConfig.name}.txt`; downloadFile(body, filename); }; + const handlePSBTDownloadClick = () => { + const body = interaction.request().toBase64(); + const timestamp = moment().format("HHmm"); + const filename = `${timestamp}-${walletName}.psbt`; + downloadFile(body, filename); + setActive(); + }; - render = () => { - const { onReceivePSBT, setError } = this.props; - return ( -
- - -
- ); + const handleWalletConfigDownloadClick = () => { + reshapeConfig(walletDetailsText); }; -} + + return ( +
+ + +
+ ); +}; ColdcardSigner.propTypes = { walletName: PropTypes.string.isRequired, diff --git a/src/components/Coldcard/ColdcardSigningButtons.jsx b/src/components/Coldcard/ColdcardSigningButtons.jsx index 41ceaa1b..c5381173 100644 --- a/src/components/Coldcard/ColdcardSigningButtons.jsx +++ b/src/components/Coldcard/ColdcardSigningButtons.jsx @@ -2,8 +2,10 @@ import React from "react"; import PropTypes from "prop-types"; import { Button } from "@mui/material"; -const ColdcardSigningButtons = (props) => { - const { handlePSBTDownloadClick, handleWalletConfigDownloadClick } = props; +const ColdcardSigningButtons = ({ + handlePSBTDownloadClick, + handleWalletConfigDownloadClick, +}) => { return ( <> @@ -215,7 +206,7 @@ ${redeemScriptLine}${scriptsSpacer}${witnessScriptLine} @@ -229,17 +220,15 @@ ${redeemScriptLine}${scriptsSpacer}${witnessScriptLine} {"your address details will be displayed here."}

); - } + }; - render() { - return ( - - - {this.body()} - - ); - } -} + return ( + + + {body()} + + ); +}; AddressGenerator.propTypes = { network: PropTypes.string.isRequired, diff --git a/src/components/CreateAddress/Conflict.jsx b/src/components/CreateAddress/Conflict.jsx index 8ce4ce2a..ce75dac1 100644 --- a/src/components/CreateAddress/Conflict.jsx +++ b/src/components/CreateAddress/Conflict.jsx @@ -11,9 +11,7 @@ import { } from "@mui/material"; import { Warning } from "@mui/icons-material"; -const Conflict = (props) => { - const { message } = props; - +const Conflict = ({ message }) => { return ( diff --git a/src/components/CreateAddress/ExtendedPublicKeyPublicKeyImporter.jsx b/src/components/CreateAddress/ExtendedPublicKeyPublicKeyImporter.jsx index 1324174e..badac0e5 100644 --- a/src/components/CreateAddress/ExtendedPublicKeyPublicKeyImporter.jsx +++ b/src/components/CreateAddress/ExtendedPublicKeyPublicKeyImporter.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState } from "react"; import PropTypes from "prop-types"; import { convertExtendedPublicKey, @@ -13,101 +13,18 @@ import { Button, TextField, FormHelperText, Box, Grid } from "@mui/material"; const DEFAULT_BIP32_PATH = "m/0"; -class ExtendedPublicKeyPublicKeyImporter extends React.Component { - constructor(props) { - super(props); - - this.state = { - bip32Path: DEFAULT_BIP32_PATH, - extendedPublicKey: "", - extendedPublicKeyError: "", - bip32PathError: "", - conversionMessage: "", - }; - } - - render = () => { - const { - bip32Path, - extendedPublicKey, - extendedPublicKeyError, - bip32PathError, - conversionMessage, - } = this.state; - return ( -
- - - - {conversionMessage !== "" && ( - - - {conversionMessage}, this may indicate an invalid network setting, - if so correct setting, remove key and try again. - - - )} - - - - - - - Use the default value if you don’t understand BIP32 paths. - - - - {!this.bip32PathIsDefault() && ( - - )} - - - - - - -
- ); - }; - - import = () => { - const { network, validatePublicKey, onImport } = this.props; - const { extendedPublicKey, bip32Path } = this.state; +const ExtendedPublicKeyPublicKeyImporter = ({ + network, + validatePublicKey, + onImport, +}) => { + const [bip32Path, setBip32Path] = useState(DEFAULT_BIP32_PATH); + const [bip32PathError, setBip32PathError] = useState(""); + const [extendedPublicKey, setExtendedPublicKey] = useState(""); + const [extendedPublicKeyError, setExtendedPublicKeyError] = useState(""); + const [conversionMessage, setConversionMessage] = useState(""); + + const importData = () => { const publicKey = deriveChildPublicKey( extendedPublicKey, bip32Path, @@ -116,56 +33,43 @@ class ExtendedPublicKeyPublicKeyImporter extends React.Component { const error = validatePublicKey(publicKey); if (error) { - this.setState({ - bip32PathError: error, - }); + setBip32PathError(error); } else { onImport({ publicKey, bip32Path }); } }; - hasBIP32PathError = () => { - const { bip32PathError } = this.state; + const hasBIP32PathError = () => { return bip32PathError !== ""; }; - hasExtendedPublicKeyError = () => { - const { extendedPublicKeyError } = this.state; + const hasExtendedPublicKeyError = () => { return extendedPublicKeyError !== ""; }; - hasError = () => this.hasBIP32PathError() || this.hasExtendedPublicKeyError(); + const hasError = () => hasBIP32PathError() || hasExtendedPublicKeyError(); - handleBIP32PathChange = (event) => { + const handleBIP32PathChange = (event) => { const nextBIP32Path = event.target.value; const error = validateBIP32Path(nextBIP32Path, { mode: "unhardened", }); - this.setState({ - bip32Path: nextBIP32Path, - bip32PathError: error ?? "", - }); + setBip32Path(nextBIP32Path); + setBip32PathError(error ?? ""); }; - bip32PathIsDefault = () => { - const { bip32Path } = this.state; + const bip32PathIsDefault = () => { return bip32Path === DEFAULT_BIP32_PATH; }; - resetBIP32Path = () => { - this.setState({ - bip32Path: DEFAULT_BIP32_PATH, - bip32PathError: "", - }); + const resetBIP32Path = () => { + setBip32Path(DEFAULT_BIP32_PATH); + setBip32PathError(""); }; - handleExtendedPublicKeyChange = (event) => { - const { network } = this.props; - - const extendedPublicKey = event.target.value; - + const handleExtendedPublicKeyChange = (event) => { const networkError = validateExtendedPublicKeyForNetwork( extendedPublicKey, network @@ -178,11 +82,10 @@ class ExtendedPublicKeyPublicKeyImporter extends React.Component { network === "testnet" ? "tpub" : "xpub" ); } catch (error) { - this.setState({ - extendedPublicKey, - extendedPublicKeyError: error.message, - conversionMessage: "", - }); + setExtendedPublicKey(event.target.value); + setExtendedPublicKeyError(error.message); + setConversionMessage(""); + return; } } @@ -192,28 +95,94 @@ class ExtendedPublicKeyPublicKeyImporter extends React.Component { network ); if (validationError !== "") { - this.setState({ - extendedPublicKey, - extendedPublicKeyError: validationError, - conversionMessage: "", - }); + setExtendedPublicKey(event.target.value); + setExtendedPublicKeyError(validationError); + setConversionMessage(""); + return; } - const conversionMessage = + const newConversionMessage = actualExtendedPublicKey === extendedPublicKey ? "" : `Your extended public key has been converted from ${extendedPublicKey.slice( 0, 4 )} to ${actualExtendedPublicKey.slice(0, 4)}`; - - this.setState({ - extendedPublicKey: actualExtendedPublicKey, - extendedPublicKeyError: "", - conversionMessage, - }); + setExtendedPublicKey(actualExtendedPublicKey); + setExtendedPublicKeyError(""); + setConversionMessage(newConversionMessage); }; -} + + return ( +
+ + + + {conversionMessage !== "" && ( + + + {conversionMessage}, this may indicate an invalid network setting, + if so correct setting, remove key and try again. + + + )} + + + + + + + Use the default value if you don’t understand BIP32 paths. + + + + {!bip32PathIsDefault() && ( + + )} + + + + + + +
+ ); +}; ExtendedPublicKeyPublicKeyImporter.propTypes = { network: PropTypes.string.isRequired, diff --git a/src/components/CreateAddress/HardwareWalletPublicKeyImporter.jsx b/src/components/CreateAddress/HardwareWalletPublicKeyImporter.jsx index 15cf0c18..8c4a109b 100644 --- a/src/components/CreateAddress/HardwareWalletPublicKeyImporter.jsx +++ b/src/components/CreateAddress/HardwareWalletPublicKeyImporter.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useCallback, useState } from "react"; import PropTypes from "prop-types"; import { UNSUPPORTED, @@ -16,34 +16,102 @@ import { Button, TextField, FormHelperText, Box, Grid } from "@mui/material"; import InteractionMessages from "../InteractionMessages"; -class HardwareWalletPublicKeyImporter extends React.Component { - constructor(props) { - super(props); - this.state = { - bip32Path: props.defaultBIP32Path, - publicKeyError: "", - bip32PathError: "", - status: this.interaction().isSupported() ? PENDING : UNSUPPORTED, - }; - } - - interaction = () => { - const { network, method, defaultBIP32Path } = this.props; - const { bip32Path } = this.state; +const HardwareWalletPublicKeyImporter = ({ + enableChangeMethod, + disableChangeMethod, + validatePublicKey, + onImport, + network, + method, + defaultBIP32Path, +}) => { + const [bip32Path, setBip32Path] = useState(defaultBIP32Path); + const [bip32PathError, setBip32PathError] = useState(""); + const [publicKeyError, setPublicKeyError] = useState(""); + + const interaction = useCallback(() => { return ExportPublicKey({ network, keystore: method, bip32Path: bip32Path ?? defaultBIP32Path, }); + }, [network, method, bip32Path]); + + const [status, setStatus] = useState( + interaction().isSupported() ? PENDING : UNSUPPORTED + ); + + const importData = async () => { + disableChangeMethod(); + + setPublicKeyError(""); + setStatus(ACTIVE); + + try { + const publicKey = await interaction().run(); + const error = validatePublicKey(publicKey); + + if (error) { + setPublicKeyError(error); + setStatus(PENDING); + } else { + onImport({ publicKey, bip32Path }); + } + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + setPublicKeyError(e.message); + setStatus(PENDING); + } + + enableChangeMethod(); }; - render = () => { - const { status, bip32Path, publicKeyError } = this.state; - const interaction = this.interaction(); + const hasBIP32PathError = () => { + return ( + bip32PathError !== "" || + interaction().hasMessagesFor({ + state: status, + level: ERROR, + code: "bip32", + }) + ); + }; + + const checkBip32PathError = () => { + if (bip32PathError !== "") { + return bip32PathError; + } + return interaction().messageTextFor({ + state: status, + level: ERROR, + code: "bip32", + }); + }; + + const handleBIP32PathChange = (event) => { + const nextBIP32Path = event.target.value; + const error = validateBIP32Path(nextBIP32Path); + + setBip32Path(nextBIP32Path); + setBip32PathError(error ?? ""); + }; + + const bip32PathIsDefault = () => { + return bip32Path === defaultBIP32Path; + }; + + const resetBIP32Path = () => { + setBip32Path(defaultBIP32Path); + setBip32PathError(""); + }; + + const renderHardWareWalletPublicKeyImporter = () => { + const newInteraction = interaction(); if (status === UNSUPPORTED) { return ( - {interaction.messageTextFor({ status })} + {newInteraction.messageTextFor({ status })} ); } @@ -56,19 +124,19 @@ class HardwareWalletPublicKeyImporter extends React.Component { label="BIP32 Path" value={bip32Path} variant="standard" - onChange={this.handleBIP32PathChange} + onChange={handleBIP32PathChange} disabled={status !== PENDING} - error={this.hasBIP32PathError()} - helperText={this.bip32PathError()} + error={hasBIP32PathError()} + helperText={checkBip32PathError()} /> - {!this.bip32PathIsDefault() && ( + {!bip32PathIsDefault() && ( @@ -99,86 +167,8 @@ class HardwareWalletPublicKeyImporter extends React.Component { ); }; - - import = async () => { - const { - enableChangeMethod, - disableChangeMethod, - validatePublicKey, - onImport, - } = this.props; - const { bip32Path } = this.state; - disableChangeMethod(); - this.setState({ publicKeyError: "", status: ACTIVE }); - try { - const publicKey = await this.interaction().run(); - const error = validatePublicKey(publicKey); - - if (error) { - this.setState({ - publicKeyError: error, - status: PENDING, - }); - } else { - onImport({ publicKey, bip32Path }); - } - } catch (e) { - // eslint-disable-next-line no-console - console.error(e); - this.setState({ publicKeyError: e.message, status: PENDING }); - } - - enableChangeMethod(); - }; - - hasBIP32PathError = () => { - const { bip32PathError, status } = this.state; - return ( - bip32PathError !== "" || - this.interaction().hasMessagesFor({ - state: status, - level: ERROR, - code: "bip32", - }) - ); - }; - - bip32PathError = () => { - const { bip32PathError, status } = this.state; - if (bip32PathError !== "") { - return bip32PathError; - } - return this.interaction().messageTextFor({ - state: status, - level: ERROR, - code: "bip32", - }); - }; - - handleBIP32PathChange = (event) => { - const nextBIP32Path = event.target.value; - const error = validateBIP32Path(nextBIP32Path); - - this.setState({ - bip32Path: nextBIP32Path, - bip32PathError: error ?? "", - }); - }; - - bip32PathIsDefault = () => { - const { bip32Path } = this.state; - const { defaultBIP32Path } = this.props; - return bip32Path === defaultBIP32Path; - }; - - resetBIP32Path = () => { - const { defaultBIP32Path } = this.props; - this.setState({ - bip32Path: defaultBIP32Path, - bip32PathError: "", - }); - }; -} + return renderHardWareWalletPublicKeyImporter(); +}; HardwareWalletPublicKeyImporter.propTypes = { network: PropTypes.string.isRequired, diff --git a/src/components/CreateAddress/HermitPublicKeyImporter.jsx b/src/components/CreateAddress/HermitPublicKeyImporter.jsx new file mode 100644 index 00000000..3d85ec4f --- /dev/null +++ b/src/components/CreateAddress/HermitPublicKeyImporter.jsx @@ -0,0 +1,80 @@ +import React, { useState } from "react"; +import PropTypes from "prop-types"; +import { HERMIT, ExportPublicKey } from "unchained-wallets"; +import { validateBIP32Path } from "unchained-bitcoin"; + +// Components +import { FormGroup, FormHelperText } from "@mui/material"; + +import HermitReader from "../Hermit/HermitReader"; + +const HermitPublicKeyImporter = ({ + network, + defaultBIP32Path, + disableChangeMethod, + validatePublicKey, + onImport, + enableChangeMethod, +}) => { + const [publicKeyError, setPublicKeyError] = useState(""); + + const interaction = () => { + return ExportPublicKey({ + keystore: HERMIT, + network, + bip32Path: defaultBIP32Path, + }); + }; + + const handleReaderStart = () => { + disableChangeMethod(); + }; + + const handleReaderSuccess = (data) => { + const { pubkey: nextPublicKey, bip32Path: nextBIP32Path } = data; + + enableChangeMethod(); + + const bip32PathError = validateBIP32Path(nextBIP32Path); + if (bip32PathError) { + setPublicKeyError(bip32PathError); + return; + } + + const newPublicKeyError = validatePublicKey(nextPublicKey); + if (newPublicKeyError) { + setPublicKeyError(newPublicKeyError); + return; + } + + onImport({ publicKey: nextPublicKey, bip32Path: nextBIP32Path }); + }; + + const handleReaderClear = () => { + setPublicKeyError(""); + enableChangeMethod(); + }; + return ( + + + {publicKeyError} + + ); +}; + +HermitPublicKeyImporter.propTypes = { + network: PropTypes.string.isRequired, + defaultBIP32Path: PropTypes.string.isRequired, + validatePublicKey: PropTypes.func.isRequired, + enableChangeMethod: PropTypes.func.isRequired, + disableChangeMethod: PropTypes.func.isRequired, + onImport: PropTypes.func.isRequired, +}; + +export default HermitPublicKeyImporter; diff --git a/src/components/CreateAddress/PublicKeyImporter.jsx b/src/components/CreateAddress/PublicKeyImporter.jsx index 300ca077..3f8e37e8 100644 --- a/src/components/CreateAddress/PublicKeyImporter.jsx +++ b/src/components/CreateAddress/PublicKeyImporter.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import PropTypes from "prop-types"; import { connect } from "react-redux"; import { validatePublicKey as baseValidatePublicKey } from "unchained-bitcoin"; @@ -29,7 +29,6 @@ import Conflict from "./Conflict"; import { setPublicKeyImporterName, setPublicKeyImporterBIP32Path, - resetPublicKeyImporterBIP32Path, setPublicKeyImporterMethod, setPublicKeyImporterPublicKey, setPublicKeyImporterFinalized, @@ -40,159 +39,42 @@ import { const XPUB = "xpub"; const TEXT = "text"; -class PublicKeyImporter extends React.Component { - constructor(props) { - super(props); - - this.state = { - disableChangeMethod: false, - }; - } - - componentDidUpdate(prevProps) { - const { number, setFinalized, addressType, publicKeyImporter } = this.props; - - if ( - prevProps.addressType !== addressType && - this.validatePublicKey(publicKeyImporter.publicKey) - ) { - setFinalized(number, false); - } - } - - title = () => { - const { number, totalSigners, publicKeyImporter, setName } = this.props; - return ( - - - - - - - -   - - - - - ); - }; - - renderImport = () => { - const { publicKeyImporter, number } = this.props; - const { disableChangeMethod } = this.state; - - return ( -
- - - {"< Select method >"} - Trezor - Ledger - Derive from extended public key - Enter as text - - - - {this.renderImportByMethod()} -
- ); - }; - - renderImportByMethod = () => { - const { publicKeyImporter, network, defaultBIP32Path } = this.props; - - if ( - publicKeyImporter.method === TREZOR || - publicKeyImporter.method === LEDGER - ) { - return ( - - ); - } - if (publicKeyImporter.method === XPUB) { - return ( - - ); - } - if (publicKeyImporter.method === TEXT) { - return ( - - ); - } - return null; - }; +const PublicKeyImporter = ({ + publicKeyImporter, + publicKeyImporters, + number, + setFinalized, + addressType, + totalSigners, + setName, + network, + defaultBIP32Path, + setMethod, + setPublicKey, + moveUp, + moveDown, + setBIP32Path, +}) => { + const [disableChangeMethod, setDisableChangeMethod] = useState(false); // // Method // - handleMethodChange = (event) => { - const { number, setMethod, setPublicKey } = this.props; + const handleMethodChange = (event) => { setMethod(number, event.target.value); setPublicKey(number, ""); }; - disableChangeMethod = () => { - this.setState({ disableChangeMethod: true }); - }; - - enableChangeMethod = () => { - this.setState({ disableChangeMethod: false }); - }; - // // State // - finalize = () => { - const { number, setFinalized } = this.props; + const finalize = () => { setFinalized(number, true); }; - reset = () => { - const { number, setPublicKey, setFinalized } = this.props; + const reset = () => { setPublicKey(number, ""); setFinalized(number, false); }; @@ -201,14 +83,12 @@ class PublicKeyImporter extends React.Component { // Position // - moveUp = (event) => { - const { moveUp, number } = this.props; + const handleMoveUp = (event) => { event.preventDefault(); moveUp(number); }; - moveDown = (event) => { - const { moveDown, number } = this.props; + const handleMoveDown = (event) => { event.preventDefault(); moveDown(number); }; @@ -217,8 +97,7 @@ class PublicKeyImporter extends React.Component { // BIP32 Path // - renderBIP32Path = () => { - const { publicKeyImporter } = this.props; + const renderBIP32Path = () => { if (publicKeyImporter.method !== TEXT) { return ( @@ -242,22 +121,21 @@ class PublicKeyImporter extends React.Component { // Public Key // - renderPublicKey = () => { - const { publicKeyImporter } = this.props; + const renderPublicKey = () => { return (

The following public key was imported:

- {this.renderBIP32Path()} + {renderBIP32Path()} +   + + + + ); - } -} + }; + + const renderImportByMethod = () => { + if ( + publicKeyImporter.method === TREZOR || + publicKeyImporter.method === LEDGER + ) { + return ( + setDisableChangeMethod(false)} + disableChangeMethod={() => setDisableChangeMethod(true)} + defaultBIP32Path={defaultBIP32Path} + network={network} + onImport={handleImport} + /> + ); + } + if (publicKeyImporter.method === XPUB) { + return ( + + ); + } + if (publicKeyImporter.method === TEXT) { + return ( + + ); + } + return null; + }; + const renderImport = () => { + return ( +
+ + + {"< Select method >"} + Trezor + Ledger + Derive from extended public key + Enter as text + + + + {renderImportByMethod()} +
+ ); + }; + + useEffect(() => { + if (validatePublicKey(publicKeyImporter.publicKey)) { + setFinalized(number, false); + } + }, [addressType]); + + const publicKeyError = validatePublicKey(publicKeyImporter.publicKey); + return ( + + + + {publicKeyImporter.method && + publicKeyImporter.method !== TEXT && + publicKeyImporter.conflict && ( + + )} + {publicKeyError.includes( + "does not support uncompressed public keys" + ) && } + {publicKeyImporter.finalized ? renderPublicKey() : renderImport()} + + + ); +}; PublicKeyImporter.propTypes = { network: PropTypes.string.isRequired, @@ -360,7 +333,6 @@ function mapStateToProps(state, ownProps) { const mapDispatchToProps = { setName: setPublicKeyImporterName, setBIP32Path: setPublicKeyImporterBIP32Path, - resetBIP32Path: resetPublicKeyImporterBIP32Path, setMethod: setPublicKeyImporterMethod, setPublicKey: setPublicKeyImporterPublicKey, setFinalized: setPublicKeyImporterFinalized, diff --git a/src/components/CreateAddress/TextPublicKeyImporter.jsx b/src/components/CreateAddress/TextPublicKeyImporter.jsx index 7a809b24..b440821a 100644 --- a/src/components/CreateAddress/TextPublicKeyImporter.jsx +++ b/src/components/CreateAddress/TextPublicKeyImporter.jsx @@ -1,77 +1,59 @@ -import React from "react"; +import React, { useState } from "react"; import PropTypes from "prop-types"; // Components import { Button, TextField, Box } from "@mui/material"; -class TextPublicKeyImporter extends React.Component { - constructor(props) { - super(props); +const TextPublicKeyImporter = ({ validatePublicKey, onImport }) => { + const [error, setError] = useState(""); + const [publicKey, setPublicKey] = useState(""); - this.state = { - error: "", - publicKey: "", - }; - } + const importData = () => { + const newError = validatePublicKey(publicKey); - render = () => { - const { error, publicKey } = this.state; - return ( - - - - - - - - ); - }; - - import = () => { - const { validatePublicKey, onImport } = this.props; - const { publicKey } = this.state; - const error = validatePublicKey(publicKey); - - if (error) { - this.setError(error); + if (newError) { + setError(newError); } else { onImport({ publicKey }); } }; - hasError = () => { - const { error } = this.state; + const hasError = () => { return error !== ""; }; - setError = (value) => { - this.setState({ error: value }); + const handleChange = (event) => { + setPublicKey(event.target.value); + setError(validatePublicKey(publicKey)); }; - handleChange = (event) => { - const publicKey = event.target.value; - const { validatePublicKey } = this.props; - const error = validatePublicKey(publicKey); - this.setState({ publicKey, error }); - }; -} + return ( + + + + + + + + ); +}; TextPublicKeyImporter.propTypes = { validatePublicKey: PropTypes.func.isRequired, diff --git a/src/components/CreateAddress/index.jsx b/src/components/CreateAddress/index.jsx index d6f0a777..d0d70dfb 100644 --- a/src/components/CreateAddress/index.jsx +++ b/src/components/CreateAddress/index.jsx @@ -15,74 +15,70 @@ import ImportAddressesButton from "../ImportAddressesButton"; import { clientPropTypes } from "../../proptypes"; import "../styles.css"; -class CreateAddress extends React.Component { - render = () => { - const { address, client } = this.props; - return ( - - - -

Address Generator

-
- - {this.renderPublicKeyImporters()} - - - - - - - - - - - - - - - - - If you plan to use this address with your own bitcoind node - you can import the address created here by switching for - "Public" to "Private". Otherwise no - action is needed here. - - } - privateNotes={ -
- -
- } - /> -
-
+const CreateAddress = ({ address, client, totalSigners }) => { + return ( + + + +

Address Generator

+ + + + + + + + + + + + + + + + + + + If you plan to use this address with your own bitcoind node + you can import the address created here by switching for + "Public" to "Private". Otherwise no action + is needed here. + + } + privateNotes={ +
+ +
+ } + /> +
+
+
+
+ ); +}; + +const PublicKeyImporters = ({ totalSigners }) => { + const publicKeyImporters = []; + for ( + let publicKeyImporterNum = 1; + publicKeyImporterNum <= totalSigners; + publicKeyImporterNum += 1 + ) { + publicKeyImporters.push( + + ); - }; - - renderPublicKeyImporters = () => { - const { totalSigners } = this.props; - const publicKeyImporters = []; - for ( - let publicKeyImporterNum = 1; - publicKeyImporterNum <= totalSigners; - publicKeyImporterNum += 1 - ) { - publicKeyImporters.push( - - - - ); - } - return publicKeyImporters; - }; -} + } + return publicKeyImporters; +}; function mapStateToProps(state) { return { diff --git a/src/components/EditableName.jsx b/src/components/EditableName.jsx index fd707141..91320d0a 100644 --- a/src/components/EditableName.jsx +++ b/src/components/EditableName.jsx @@ -1,28 +1,49 @@ -import React from "react"; +import React, { useState } from "react"; import PropTypes from "prop-types"; import { Grid, IconButton, TextField } from "@mui/material"; import { Check, Clear, Edit } from "@mui/icons-material"; -class EditableName extends React.Component { - constructor(props) { - super(props); - this.state = { - editing: false, - newName: "", - error: "", - }; - } +const EditableName = ({ name, setName, number }) => { + const [editing, setEditing] = useState(false); + const [newName, setNewName] = useState(name); + const [error, setError] = useState(""); - componentDidMount = () => { - const { name } = this.props; - this.setState({ newName: name }); + const hasError = () => { + return error !== ""; + }; + + const startEditing = (event) => { + event.preventDefault(); + setEditing(true); + setNewName(name); + }; + + const handleChange = (event) => { + const updatedName = event.target.value; + + if ( + updatedName === null || + updatedName === undefined || + updatedName === "" + ) { + setError("Name cannot be blank."); + } + setNewName(updatedName); }; - render = () => { - const { name } = this.props; - const { editing, newName, error } = this.state; + const submit = () => { + setName(number, newName); + setEditing(false); + }; + + const cancel = () => { + setEditing(false); + setNewName(name); + setError(""); + }; + + const renderEditableName = () => { if (editing) { - //
return ( @@ -31,12 +52,12 @@ class EditableName extends React.Component { label="Name" value={newName} variant="standard" - onChange={this.handleChange} + onChange={handleChange} onFocus={(event) => { setTimeout(event.target.select.bind(event.target), 20); }} - onKeyDown={(e) => (e.key === "Enter" ? this.submit() : null)} - error={this.hasError()} + onKeyDown={(e) => (e.key === "Enter" ? submit() : null)} + error={hasError()} helperText={error} /> @@ -45,8 +66,8 @@ class EditableName extends React.Component { @@ -57,7 +78,7 @@ class EditableName extends React.Component { data-cy="cancel-button" color="secondary" size="small" - onClick={this.cancel} + onClick={cancel} > @@ -67,11 +88,7 @@ class EditableName extends React.Component { } return ( - +   @@ -79,46 +96,15 @@ class EditableName extends React.Component { {name} ); }; - - hasError = () => { - const { error } = this.state; - return error !== ""; - }; - - startEditing = (event) => { - const { name } = this.props; - event.preventDefault(); - this.setState({ editing: true, newName: name }); - }; - - handleChange = (event) => { - const newName = event.target.value; - let error = ""; - if (newName === null || newName === undefined || newName === "") { - error = "Name cannot be blank."; - } - this.setState({ newName, error }); - }; - - submit = () => { - const { setName, number } = this.props; - const { newName } = this.state; - setName(number, newName); - this.setState({ editing: false }); - }; - - cancel = () => { - const { name } = this.props; - this.setState({ error: "", newName: name, editing: false }); - }; -} + return renderEditableName(); +}; EditableName.propTypes = { number: PropTypes.number.isRequired, diff --git a/src/components/ErrorNotification.jsx b/src/components/ErrorNotification.jsx index c52cfc3d..f310d93d 100644 --- a/src/components/ErrorNotification.jsx +++ b/src/components/ErrorNotification.jsx @@ -6,9 +6,7 @@ import { Snackbar, IconButton } from "@mui/material"; import { Close } from "@mui/icons-material"; import { clearErrorNotification as clearErrorNotificationAction } from "../actions/errorNotificationActions"; -const ErrorNotificationBase = (props) => { - const { open, message, clearErrorNotification } = props; - +const ErrorNotificationBase = ({ open, message, clearErrorNotification }) => { return ( { +const MultisigDetails = ({ network, multisig, showAddress }) => { + const renderScript = (name, script) => { const hex = scriptToHex(script); const ops = scriptToOps(script); return ( @@ -38,59 +38,56 @@ class MultisigDetails extends React.Component { ); }; - render() { - const { network, multisig, showAddress } = this.props; - const { address } = multisig; - const redeemScript = multisigRedeemScript(multisig); - const witnessScript = multisigWitnessScript(multisig); - return ( - - {showAddress && Address} + const { address } = multisig; + const redeemScript = multisigRedeemScript(multisig); + const witnessScript = multisigWitnessScript(multisig); + return ( + + {showAddress && Address} - - - {showAddress && ( - - -   - {externalLink( - blockExplorerAddressURL(address, network), - - )} - - )} + + + {showAddress && ( + + +   + {externalLink( + blockExplorerAddressURL(address, network), + + )} + + )} - - - - + + + + - - - + + + - - - + + + - - - + + - + + - {this.renderScript("Script", multisig)} - {redeemScript && this.renderScript("Redeem Script", redeemScript)} - {witnessScript && this.renderScript("Witness Script", witnessScript)} - - ); - } -} + {renderScript("Script", multisig)} + {redeemScript && renderScript("Redeem Script", redeemScript)} + {witnessScript && renderScript("Witness Script", witnessScript)} + + ); +}; MultisigDetails.propTypes = { multisig: PropTypes.shape({ diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index 5e9a9527..ede40631 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -34,20 +34,15 @@ const useStyles = makeStyles((theme) => ({ }, })); -// This needs to be a class component because it uses a ref -// eslint-disable-next-line react/prefer-stateless-function -class NavItem extends React.Component { - render() { - const { href, title, classes, handleClose } = this.props; - return ( - - - {title} - - - ); - } -} +const NavItem = ({ href, title, classes, handleClose }) => { + return ( + + + {title} + + + ); +}; NavItem.propTypes = { href: PropTypes.string.isRequired, diff --git a/src/components/NetworkPicker.jsx b/src/components/NetworkPicker.jsx index e92884d1..ef31d0e9 100644 --- a/src/components/NetworkPicker.jsx +++ b/src/components/NetworkPicker.jsx @@ -19,53 +19,49 @@ import { // Actions import { setNetwork as setNetworkAction } from "../actions/settingsActions"; -class NetworkPicker extends React.Component { - handleNetworkChange = (event) => { - const { setNetwork } = this.props; +const NetworkPicker = ({ setNetwork, network, frozen }) => { + const handleNetworkChange = (event) => { setNetwork(event.target.value); }; - render() { - const { network, frozen } = this.props; - return ( - - - - - - } - name="network" - value="mainnet" - label={Mainnet} - onChange={this.handleNetworkChange} - checked={network === MAINNET} - disabled={frozen} - /> - } - name="network" - value="testnet" - label="Testnet" - onChange={this.handleNetworkChange} - checked={network === TESTNET} - disabled={frozen} - /> - - - - Choose 'Mainnet' if you don't understand the - difference. - - - - - - ); - } -} + return ( + + + + + + } + name="network" + value="mainnet" + label={Mainnet} + onChange={handleNetworkChange} + checked={network === MAINNET} + disabled={frozen} + /> + } + name="network" + value="testnet" + label="Testnet" + onChange={handleNetworkChange} + checked={network === TESTNET} + disabled={frozen} + /> + + + + Choose 'Mainnet' if you don't understand the + difference. + + + + + + ); +}; NetworkPicker.propTypes = { network: PropTypes.string.isRequired, diff --git a/src/components/QuorumPicker.jsx b/src/components/QuorumPicker.jsx index 79734470..49c1b1a0 100644 --- a/src/components/QuorumPicker.jsx +++ b/src/components/QuorumPicker.jsx @@ -23,14 +23,39 @@ import "./styles.css"; const MAX_TOTAL_SIGNERS = 7; -class QuorumPicker extends React.Component { - renderIncrementRequiredSigners = () => { - const { requiredSigners, totalSigners, frozen } = this.props; +const QuorumPicker = ({ + frozen, + requiredSigners, + setRequiredSigners, + totalSigners, + setTotalSigners, +}) => { + const handleIncrementRequiredSigners = (event) => { + setRequiredSigners(requiredSigners + 1); + event.preventDefault(); + }; + + const handleDecrementRequiredSigners = (event) => { + setRequiredSigners(requiredSigners - 1); + event.preventDefault(); + }; + + const handleIncrementTotalSigners = (event) => { + setTotalSigners(totalSigners + 1); + event.preventDefault(); + }; + + const handleDecrementTotalSigners = (event) => { + setTotalSigners(totalSigners - 1); + event.preventDefault(); + }; + + const renderIncrementRequiredSigners = () => { const disabled = requiredSigners === totalSigners || frozen; return ( @@ -38,13 +63,12 @@ class QuorumPicker extends React.Component { ); }; - renderDecrementRequiredSigners = () => { - const { requiredSigners, frozen } = this.props; + const renderDecrementRequiredSigners = () => { const disabled = requiredSigners === 1 || frozen; return ( @@ -52,13 +76,12 @@ class QuorumPicker extends React.Component { ); }; - renderIncrementTotalSigners = () => { - const { totalSigners, frozen } = this.props; + const renderIncrementTotalSigners = () => { const disabled = totalSigners === MAX_TOTAL_SIGNERS || frozen; return ( @@ -66,14 +89,13 @@ class QuorumPicker extends React.Component { ); }; - renderDecrementTotalSigners = () => { - const { requiredSigners, totalSigners, frozen } = this.props; + const renderDecrementTotalSigners = () => { const disabled = totalSigners === requiredSigners || totalSigners === 2 || frozen; return ( @@ -81,109 +103,69 @@ class QuorumPicker extends React.Component { ); }; - handleIncrementRequiredSigners = (event) => { - const { requiredSigners, setRequiredSigners } = this.props; - setRequiredSigners(requiredSigners + 1); - event.preventDefault(); - }; + return ( + + + + + + +   + - handleDecrementRequiredSigners = (event) => { - const { requiredSigners, setRequiredSigners } = this.props; - setRequiredSigners(requiredSigners - 1); - event.preventDefault(); - }; + + {renderIncrementRequiredSigners()} - handleIncrementTotalSigners = (event) => { - const { totalSigners, setTotalSigners } = this.props; - setTotalSigners(totalSigners + 1); - event.preventDefault(); - }; + + {requiredSigners} + - handleDecrementTotalSigners = (event) => { - const { totalSigners, setTotalSigners } = this.props; - setTotalSigners(totalSigners - 1); - event.preventDefault(); - }; + + +

Required

+
+
- render() { - const { requiredSigners, totalSigners } = this.props; + {renderDecrementRequiredSigners()} +
- return ( - - - - - - -   + + + of + - - {this.renderIncrementRequiredSigners()} - - - {requiredSigners} - - - - -

Required

-
-
- - {this.renderDecrementRequiredSigners()} -
+ + {renderIncrementTotalSigners()} - - - of - + + {totalSigners} - - {this.renderIncrementTotalSigners()} - - - {totalSigners} - - - - -

Total

-
-
- - {this.renderDecrementTotalSigners()} -
- -   + + +

Total

+
+ + {renderDecrementTotalSigners()}
-
-
-
- ); - } -} + +   + +
+
+
+
+ ); +}; QuorumPicker.propTypes = { totalSigners: PropTypes.number.isRequired, diff --git a/src/components/StartingAddressIndexPicker.jsx b/src/components/StartingAddressIndexPicker.jsx index 6d5de617..cd683740 100644 --- a/src/components/StartingAddressIndexPicker.jsx +++ b/src/components/StartingAddressIndexPicker.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState } from "react"; import PropTypes from "prop-types"; import { connect } from "react-redux"; import { validateBIP32Index } from "unchained-bitcoin"; @@ -19,18 +19,17 @@ import { import { wrappedActions } from "../actions/utils"; import { SET_STARTING_ADDRESS_INDEX } from "../actions/settingsActions"; -class StartingAddressIndexPicker extends React.Component { - constructor(props) { - super(props); +const StartingAddressIndexPicker = ({ + startingAddressIndex, + setStartingAddressIndex, +}) => { + const [customIndex, setCustomIndex] = useState(startingAddressIndex !== 0); + const [startingAddressIndexField, setStartingAddressIndexField] = + useState(startingAddressIndex); + const [startingAddressIndexError, setStartingAddressIndexError] = + useState(""); - this.state = { - customIndex: props.startingAddressIndex !== 0, - startingAddressIndexField: props.startingAddressIndex, - }; - } - - handleIndexChange = (event) => { - const { setStartingAddressIndex } = this.props; + const handleIndexChange = (event) => { const index = event.target.value; const error = validateBIP32Index(index, { mode: "unhardened" }).replace( "BIP32", @@ -39,80 +38,70 @@ class StartingAddressIndexPicker extends React.Component { if (!error && index) { setStartingAddressIndex(parseInt(index, 10)); } - this.setState({ - startingAddressIndexField: index, - startingAddressIndexError: error, - }); + setStartingAddressIndexField(index); + setStartingAddressIndexError(error); }; - handleCustomIndexChange = (event) => { - const customIndex = event.target.value === "true"; - this.setState({ customIndex }); + const handleCustomIndexChange = (event) => { + setCustomIndex(event.target.value === "true"); }; - render() { - const { - customIndex, - startingAddressIndexField, - startingAddressIndexError, - } = this.state; - return ( - - - - - - - - + return ( + + + + + + + + + } + name="customIndex" + value="false" + label={Default (0)} + onChange={handleCustomIndexChange} + checked={!customIndex} + /> + } name="customIndex" - value="false" - label={Default (0)} - onChange={this.handleCustomIndexChange} - checked={!customIndex} + value="true" + label="Custom" + onChange={handleCustomIndexChange} + checked={customIndex} /> - - } - name="customIndex" - value="true" - label="Custom" - onChange={this.handleCustomIndexChange} - checked={customIndex} - /> - - - Use the default value if you do not understand how to use - starting address index. - - - {customIndex && ( - - )} - - - - - ); - } -} + + + Use the default value if you do not understand how to use + starting address index. + + + {customIndex && ( + + )} + + + + + ); +}; StartingAddressIndexPicker.propTypes = { startingAddressIndex: PropTypes.number.isRequired, diff --git a/src/components/UnsignedTransaction.jsx b/src/components/UnsignedTransaction.jsx index 680dfef2..4893e075 100644 --- a/src/components/UnsignedTransaction.jsx +++ b/src/components/UnsignedTransaction.jsx @@ -1,27 +1,28 @@ -import React from "react"; +import React, { useState } from "react"; import PropTypes from "prop-types"; import { connect } from "react-redux"; import { Button } from "@mui/material"; import Copyable from "./Copyable"; -class UnsignedTransaction extends React.Component { - constructor(props) { - super(props); - this.state = { - showUnsignedTransaction: false, - }; - } +const UnsignedTransaction = ({ unsignedTransaction, unsignedPSBT }) => { + const [showUnsignedTransaction, setShowUnsignedTransaction] = useState(false); - render = () => { - const { showUnsignedTransaction } = this.state; - const { unsignedTransaction, unsignedPSBT } = this.props; + const handleShowUnsignedTransaction = () => { + setShowUnsignedTransaction(true); + }; + + const handleHideUnsignedTransaction = () => { + setShowUnsignedTransaction(false); + }; + + const renderUnsignedTransaction = () => { if (showUnsignedTransaction) { const hex = unsignedTransaction.toHex(); return (
- @@ -40,21 +41,14 @@ class UnsignedTransaction extends React.Component { } return ( - ); }; - - handleShowUnsignedTransaction = () => { - this.setState({ showUnsignedTransaction: true }); - }; - - handleHideUnsignedTransaction = () => { - this.setState({ showUnsignedTransaction: false }); - }; -} + return renderUnsignedTransaction(); +}; UnsignedTransaction.propTypes = { unsignedTransaction: PropTypes.shape({