Merge branch 'dev' into feature/fmp_to_postgresql_t17

This commit is contained in:
abpwrs committed 2020-04-27 23:14:48 -05:00
commit 5de0b8641f
36 files changed
+15779 -6086

No files matched your search

+1
View File
@@ -17,6 +17,7 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- run: docker-compose -f dev-docker-compose.yml up --detach
- run: docker exec haiti_pwa npm run lint
- run: docker exec haiti_pwa npm run test:cov
- run: docker exec haiti_pwa npm run cy:run
- run: docker exec haiti_pwa npm run build
+25
View File
@@ -0,0 +1,25 @@
module.exports = {
parser: "@typescript-eslint/parser", // Specifies the ESLint parser
extends: [
"plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react
"plugin:@typescript-eslint/recommended", // Uses the recommended rules from the @typescript-eslint/eslint-plugin
"prettier/@typescript-eslint", // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
"plugin:prettier/recommended", // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
],
parserOptions: {
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
sourceType: "module", // Allows for the use of imports
ecmaFeatures: {
jsx: true, // Allows for the parsing of JSX
},
},
rules: {
"@typescript-eslint/no-empty-interface": "off",
"@typescript-eslint/ban-ts-ignore": "off",
},
settings: {
react: {
version: "detect", // Tells eslint-plugin-react to automatically detect the version of React to use
},
},
};
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
semi: false,
trailingComma: 'all',
singleQuote: true,
printWidth: 80,
tabWidth: 4,
}
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
}
+31
View File
@@ -0,0 +1,31 @@
{
"eslint.autoFixOnSave": true,
"eslint.validate": [
"javascript",
"javascriptreact",
{
"language": "typescript",
"autoFix": true
},
{
"language": "typescriptreact",
"autoFix": true
}
],
"editor.formatOnSave": true,
"[javascript]": {
"editor.formatOnSave": true
},
"[javascriptreact]": {
"editor.formatOnSave": true
},
"[typescript]": {
"editor.formatOnSave": true
},
"[typescriptreact]": {
"editor.formatOnSave": true
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
+5 -5
View File
@@ -3,10 +3,10 @@
/// <reference types="cypress" />
describe('Homepage', () => {
it('shows login button', function () {
cy.visit('http://localhost:3000')
cy.get('.am-button').contains('Login')
})
it('shows login button', function () {
cy.visit('http://localhost:3000')
cy.get('.am-button').contains('Login')
})
})
export {};
export {}
+443 -140
View File
@@ -1158,6 +1158,34 @@
}
}
},
"@cypress/request": {
"version": "2.88.5",
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.5.tgz",
"integrity": "sha512-TzEC1XMi1hJkywWpRfD2clreTa/Z+lOrXDCxxBTBPEcY5azdPi56A6Xw+O4tWJnaJH3iIE7G5aDXZC6JgRZLcA==",
"dev": true,
"requires": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
"caseless": "~0.12.0",
"combined-stream": "~1.0.6",
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"form-data": "~2.3.2",
"har-validator": "~5.1.3",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.19",
"oauth-sign": "~0.9.0",
"performance-now": "^2.1.0",
"qs": "~6.5.2",
"safe-buffer": "^5.1.2",
"tough-cookie": "~2.5.0",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
}
},
"@cypress/webpack-preprocessor": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/@cypress/webpack-preprocessor/-/webpack-preprocessor-4.1.3.tgz",
@@ -1211,9 +1239,9 @@
"integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA=="
},
"@hapi/hoek": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.0.tgz",
"integrity": "sha512-7XYT10CZfPsH7j9F1Jmg1+d0ezOux2oM2GfArAzLwWe4mE2Dr3hVjsAL6+TFY49RRJlCdJDMw3nJsLFroTc8Kw=="
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz",
"integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow=="
},
"@hapi/joi": {
"version": "15.1.1",
@@ -1653,6 +1681,34 @@
"@babel/types": "^7.3.0"
}
},
"@types/blob-util": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@types/blob-util/-/blob-util-1.3.3.tgz",
"integrity": "sha512-4ahcL/QDnpjWA2Qs16ZMQif7HjGP2cw3AGjHabybjw7Vm1EKu+cfQN1D78BaZbS1WJNa1opSMF5HNMztx7lR0w==",
"dev": true
},
"@types/bluebird": {
"version": "3.5.29",
"resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.29.tgz",
"integrity": "sha512-kmVtnxTuUuhCET669irqQmPAez4KFnFVKvpleVRyfC3g+SHD1hIkFZcWLim9BVcwUBLO59o8VZE4yGCmTif8Yw==",
"dev": true
},
"@types/chai": {
"version": "4.2.7",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.7.tgz",
"integrity": "sha512-luq8meHGYwvky0O7u0eQZdA7B4Wd9owUCqvbw2m3XCrCU8mplYOujMBbvyS547AxJkC+pGnd0Cm15eNxEUNU8g==",
"dev": true
},
"@types/chai-jquery": {
"version": "1.1.40",
"resolved": "https://registry.npmjs.org/@types/chai-jquery/-/chai-jquery-1.1.40.tgz",
"integrity": "sha512-mCNEZ3GKP7T7kftKeIs7QmfZZQM7hslGSpYzKbOlR2a2HCFf9ph4nlMRA9UnuOETeOQYJVhJQK7MwGqNZVyUtQ==",
"dev": true,
"requires": {
"@types/chai": "*",
"@types/jquery": "*"
}
},
"@types/color-name": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
@@ -1678,6 +1734,11 @@
"@types/node": "*"
}
},
"@types/history": {
"version": "4.7.5",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.5.tgz",
"integrity": "sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw=="
},
"@types/istanbul-lib-coverage": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz",
@@ -1708,16 +1769,37 @@
"jest-diff": "^24.3.0"
}
},
"@types/jquery": {
"version": "3.3.31",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.31.tgz",
"integrity": "sha512-Lz4BAJihoFw5nRzKvg4nawXPzutkv7wmfQ5121avptaSIXlDNJCUuxZxX/G+9EVidZGuO0UBlk+YjKbwRKJigg==",
"dev": true,
"requires": {
"@types/sizzle": "*"
}
},
"@types/json-schema": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz",
"integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA=="
},
"@types/lodash": {
"version": "4.14.149",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz",
"integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==",
"dev": true
},
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA=="
},
"@types/mocha": {
"version": "5.2.7",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz",
"integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==",
"dev": true
},
"@types/node": {
"version": "12.12.26",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.26.tgz",
@@ -1755,6 +1837,25 @@
"@types/react": "*"
}
},
"@types/react-router": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.5.tgz",
"integrity": "sha512-RZPdCtZympi6X7EkGyaU7ISiAujDYTWgqMF9owE3P6efITw27IWQykcti0BvA5h4Mu1LLl5rxrpO3r8kHyUZ/Q==",
"requires": {
"@types/history": "*",
"@types/react": "*"
}
},
"@types/react-router-dom": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.4.tgz",
"integrity": "sha512-LO0z5qqSfWdYtCNsRm8/OMnnkv52hwADJKrAfpKIyfHclORllcgAMGypEA7ajHm38+jOonKLx8nPygNAAZGxtg==",
"requires": {
"@types/history": "*",
"@types/react": "*",
"@types/react-router": "*"
}
},
"@types/react-test-renderer": {
"version": "16.9.2",
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-16.9.2.tgz",
@@ -1764,6 +1865,22 @@
"@types/react": "*"
}
},
"@types/sinon": {
"version": "7.5.1",
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.5.1.tgz",
"integrity": "sha512-EZQUP3hSZQyTQRfiLqelC9NMWd1kqLcmQE0dMiklxBkgi84T+cHOhnKpgk4NnOWpGX863yE6+IaGnOXUNFqDnQ==",
"dev": true
},
"@types/sinon-chai": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.3.tgz",
"integrity": "sha512-TOUFS6vqS0PVL1I8NGVSNcFaNJtFoyZPXZ5zur+qlhDfOmQECZZM4H4kKgca6O8L+QceX/ymODZASfUfn+y4yQ==",
"dev": true,
"requires": {
"@types/chai": "*",
"@types/sinon": "*"
}
},
"@types/sizzle": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz",
@@ -1816,42 +1933,42 @@
"integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw=="
},
"@typescript-eslint/eslint-plugin": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.19.0.tgz",
"integrity": "sha512-u7IcQ9qwsB6U806LupZmINRnQjC+RJyv36sV/ugaFWMHTbFm/hlLTRx3gGYJgHisxcGSTnf+I/fPDieRMhPSQQ==",
"version": "2.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.28.0.tgz",
"integrity": "sha512-w0Ugcq2iatloEabQP56BRWJowliXUP5Wv6f9fKzjJmDW81hOTBxRoJ4LoEOxRpz9gcY51Libytd2ba3yLmSOfg==",
"requires": {
"@typescript-eslint/experimental-utils": "2.19.0",
"eslint-utils": "^1.4.3",
"@typescript-eslint/experimental-utils": "2.28.0",
"functional-red-black-tree": "^1.0.1",
"regexpp": "^3.0.0",
"tsutils": "^3.17.1"
}
},
"@typescript-eslint/experimental-utils": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.19.0.tgz",
"integrity": "sha512-zwpg6zEOPbhB3+GaQfufzlMUOO6GXCNZq6skk+b2ZkZAIoBhVoanWK255BS1g5x9bMwHpLhX0Rpn5Fc3NdCZdg==",
"version": "2.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.28.0.tgz",
"integrity": "sha512-4SL9OWjvFbHumM/Zh/ZeEjUFxrYKtdCi7At4GyKTbQlrj1HcphIDXlje4Uu4cY+qzszR5NdVin4CCm6AXCjd6w==",
"requires": {
"@types/json-schema": "^7.0.3",
"@typescript-eslint/typescript-estree": "2.19.0",
"eslint-scope": "^5.0.0"
"@typescript-eslint/typescript-estree": "2.28.0",
"eslint-scope": "^5.0.0",
"eslint-utils": "^2.0.0"
}
},
"@typescript-eslint/parser": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.19.0.tgz",
"integrity": "sha512-s0jZoxAWjHnuidbbN7aA+BFVXn4TCcxEVGPV8lWMxZglSs3NRnFFAlL+aIENNmzB2/1jUJuySi6GiM6uACPmpg==",
"version": "2.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.28.0.tgz",
"integrity": "sha512-RqPybRDquui9d+K86lL7iPqH6Dfp9461oyqvlXMNtap+PyqYbkY5dB7LawQjDzot99fqzvS0ZLZdfe+1Bt3Jgw==",
"requires": {
"@types/eslint-visitor-keys": "^1.0.0",
"@typescript-eslint/experimental-utils": "2.19.0",
"@typescript-eslint/typescript-estree": "2.19.0",
"@typescript-eslint/experimental-utils": "2.28.0",
"@typescript-eslint/typescript-estree": "2.28.0",
"eslint-visitor-keys": "^1.1.0"
}
},
"@typescript-eslint/typescript-estree": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.19.0.tgz",
"integrity": "sha512-n6/Xa37k0jQdwpUszffi19AlNbVCR0sdvCs3DmSKMD7wBttKY31lhD2fug5kMD91B2qW4mQldaTEc1PEzvGu8w==",
"version": "2.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.28.0.tgz",
"integrity": "sha512-HDr8MP9wfwkiuqzRVkuM3BeDrOC4cKbO5a6BymZBHUt5y/2pL0BXD6I/C/ceq2IZoHWhcASk+5/zo+dwgu9V8Q==",
"requires": {
"debug": "^4.1.1",
"eslint-visitor-keys": "^1.1.0",
@@ -2067,9 +2184,9 @@
}
},
"acorn": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
"integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ=="
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
"integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg=="
},
"acorn-globals": {
"version": "4.3.4",
@@ -2088,9 +2205,9 @@
}
},
"acorn-jsx": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz",
"integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw=="
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz",
"integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ=="
},
"acorn-walk": {
"version": "6.2.0",
@@ -4374,13 +4491,24 @@
"integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk="
},
"cypress": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-4.2.0.tgz",
"integrity": "sha512-8LdreL91S/QiTCLYLNbIjLL8Ht4fJmu/4HGLxUI20Tc7JSfqEfCmXELrRfuPT0kjosJwJJZacdSji9XSRkPKUw==",
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-4.4.0.tgz",
"integrity": "sha512-ZpsV3pVemANGi4Cxu0UIqFv23uHdDJZYlKY+8P/eixujCpI1TQ5RSPBp2grfV3ZvlGYrOXPJY44j9iEh1xoQug==",
"dev": true,
"requires": {
"@cypress/listr-verbose-renderer": "0.4.1",
"@cypress/request": "2.88.5",
"@cypress/xvfb": "1.2.4",
"@types/blob-util": "1.3.3",
"@types/bluebird": "3.5.29",
"@types/chai": "4.2.7",
"@types/chai-jquery": "1.1.40",
"@types/jquery": "3.3.31",
"@types/lodash": "4.14.149",
"@types/minimatch": "3.0.3",
"@types/mocha": "5.2.7",
"@types/sinon": "7.5.1",
"@types/sinon-chai": "3.2.3",
"@types/sizzle": "2.3.2",
"arch": "2.1.1",
"bluebird": "3.7.2",
@@ -4394,7 +4522,7 @@
"eventemitter2": "4.1.2",
"execa": "1.0.0",
"executable": "4.1.1",
"extract-zip": "1.6.7",
"extract-zip": "1.7.0",
"fs-extra": "8.1.0",
"getos": "3.1.4",
"is-ci": "2.0.0",
@@ -4403,12 +4531,11 @@
"listr": "0.14.3",
"lodash": "4.17.15",
"log-symbols": "3.0.0",
"minimist": "1.2.2",
"minimist": "1.2.5",
"moment": "2.24.0",
"ospath": "1.2.2",
"pretty-bytes": "5.3.0",
"ramda": "0.26.1",
"request": "github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16",
"request-progress": "3.0.0",
"supports-color": "7.1.0",
"tmp": "0.1.0",
@@ -4429,39 +4556,6 @@
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"minimist": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.2.tgz",
"integrity": "sha512-rIqbOrKb8GJmx/5bc2M0QchhUouMXSpd1RTclXsB41JdL+VtnojfaJR+h7F9k18/4kHUsBFgk80Uk+q569vjPA==",
"dev": true
},
"request": {
"version": "github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16",
"from": "github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16",
"dev": true,
"requires": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
"caseless": "~0.12.0",
"combined-stream": "~1.0.6",
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"form-data": "~2.3.2",
"har-validator": "~5.1.3",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.19",
"oauth-sign": "~0.9.0",
"performance-now": "^2.1.0",
"qs": "~6.5.2",
"safe-buffer": "^5.1.2",
"tough-cookie": "~2.5.0",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
}
},
"supports-color": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
@@ -4532,6 +4626,11 @@
"integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==",
"dev": true
},
"debounce": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz",
"integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg=="
},
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
@@ -4911,6 +5010,37 @@
}
}
},
"easy-peasy": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/easy-peasy/-/easy-peasy-3.3.0.tgz",
"integrity": "sha512-wkTDAG+nRy+4g8Z3/k1vuTyrhxet19oY5MCmuRgOObm8dtZ2m+g5Mk/sdf3UWPuyx/sKIYH5Fy/HXab8iNKjTQ==",
"requires": {
"debounce": "^1.2.0",
"immer-peasy": "3.1.3",
"is-plain-object": "^3.0.0",
"memoizerific": "^1.11.3",
"prop-types": "^15.6.2",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
"symbol-observable": "^1.2.0",
"ts-toolbelt": "^6.1.6"
},
"dependencies": {
"is-plain-object": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz",
"integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==",
"requires": {
"isobject": "^4.0.0"
}
},
"isobject": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz",
"integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA=="
}
}
},
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -5176,10 +5306,18 @@
"v8-compile-cache": "^2.0.3"
},
"dependencies": {
"eslint-utils": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
"integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
"requires": {
"eslint-visitor-keys": "^1.1.0"
}
},
"globals": {
"version": "12.3.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-12.3.0.tgz",
"integrity": "sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==",
"version": "12.4.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
"integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
"requires": {
"type-fest": "^0.8.1"
}
@@ -5205,6 +5343,23 @@
}
}
},
"eslint-config-prettier": {
"version": "6.10.1",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.10.1.tgz",
"integrity": "sha512-svTy6zh1ecQojvpbJSgH3aei/Rt7C6i090l5f2WQ4aB05lYHeZIR1qL4wZyyILTbtmnbHP5Yn8MrsOJMGa8RkQ==",
"dev": true,
"requires": {
"get-stdin": "^6.0.0"
},
"dependencies": {
"get-stdin": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz",
"integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==",
"dev": true
}
}
},
"eslint-config-react-app": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-5.2.0.tgz",
@@ -5482,6 +5637,15 @@
}
}
},
"eslint-plugin-prettier": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.3.tgz",
"integrity": "sha512-+HG5jmu/dN3ZV3T6eCD7a4BlAySdN7mLIbJYo0z1cFQuI+r2DiTJEFeF68ots93PsnrMxbzIZ2S/ieX+mkrBeQ==",
"dev": true,
"requires": {
"prettier-linter-helpers": "^1.0.0"
}
},
"eslint-plugin-react": {
"version": "7.18.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.18.0.tgz",
@@ -5523,9 +5687,9 @@
}
},
"eslint-utils": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
"integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz",
"integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==",
"requires": {
"eslint-visitor-keys": "^1.1.0"
}
@@ -5536,12 +5700,12 @@
"integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A=="
},
"espree": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz",
"integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==",
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz",
"integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==",
"requires": {
"acorn": "^7.1.0",
"acorn-jsx": "^5.1.0",
"acorn": "^7.1.1",
"acorn-jsx": "^5.2.0",
"eslint-visitor-keys": "^1.1.0"
}
},
@@ -5551,11 +5715,18 @@
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"esquery": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
"integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz",
"integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==",
"requires": {
"estraverse": "^4.0.0"
"estraverse": "^5.1.0"
},
"dependencies": {
"estraverse": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz",
"integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw=="
}
}
},
"esrecurse": {
@@ -5900,15 +6071,15 @@
}
},
"extract-zip": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz",
"integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=",
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz",
"integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==",
"dev": true,
"requires": {
"concat-stream": "1.6.2",
"debug": "2.6.9",
"mkdirp": "0.5.1",
"yauzl": "2.4.1"
"concat-stream": "^1.6.2",
"debug": "^2.6.9",
"mkdirp": "^0.5.4",
"yauzl": "^2.10.0"
},
"dependencies": {
"debug": {
@@ -5925,15 +6096,6 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
},
"yauzl": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",
"integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=",
"dev": true,
"requires": {
"fd-slicer": "~1.0.1"
}
}
}
},
@@ -5947,6 +6109,12 @@
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
"integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
},
"fast-diff": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
"dev": true
},
"fast-glob": {
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz",
@@ -6037,9 +6205,9 @@
}
},
"fd-slicer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
"integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
"integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
"dev": true,
"requires": {
"pend": "~1.2.0"
@@ -6165,9 +6333,9 @@
}
},
"flatted": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
"integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg=="
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
"integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA=="
},
"flatten": {
"version": "1.0.3",
@@ -6636,6 +6804,11 @@
"resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
"integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE="
},
"gud": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz",
"integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw=="
},
"gzip-size": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz",
@@ -6771,6 +6944,19 @@
"resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
"integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ=="
},
"history": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
"requires": {
"@babel/runtime": "^7.1.2",
"loose-envify": "^1.2.0",
"resolve-pathname": "^3.0.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0",
"value-equal": "^1.0.1"
}
},
"hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@@ -6781,6 +6967,14 @@
"minimalistic-crypto-utils": "^1.0.1"
}
},
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"requires": {
"react-is": "^16.7.0"
}
},
"hosted-git-info": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz",
@@ -7028,6 +7222,11 @@
"resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz",
"integrity": "sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg=="
},
"immer-peasy": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/immer-peasy/-/immer-peasy-3.1.3.tgz",
"integrity": "sha512-WzoZ96A93jOmcDOLNChMWAqy+ZU8vEYQx2DcKjgo7P5SToiJs+GL+5yQbWzH8X02Lhvv6xrGgVNa1xbki66Eow=="
},
"import-cwd": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
@@ -8959,12 +9158,12 @@
}
},
"loader-fs-cache": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.2.tgz",
"integrity": "sha512-70IzT/0/L+M20jUlEqZhZyArTU6VKLRTYRDAYN26g4jfzpJqjipLL3/hgYpySqI9PwsVRHHFja0LfEmsx9X2Cw==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.3.tgz",
"integrity": "sha512-ldcgZpjNJj71n+2Mf6yetz+c9bM4xpKtNds4LbqXzU/PTdeAX0g3ytnU1AJMEcTk2Lex4Smpe3Q/eCTsvUBxbA==",
"requires": {
"find-cache-dir": "^0.1.1",
"mkdirp": "0.5.1"
"mkdirp": "^0.5.1"
},
"dependencies": {
"find-cache-dir": {
@@ -9293,6 +9492,11 @@
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
"integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0="
},
"map-or-similar": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/map-or-similar/-/map-or-similar-1.5.0.tgz",
"integrity": "sha1-beJlMXSt+12e3DPGnT6Sobdvrwg="
},
"map-visit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
@@ -9331,6 +9535,14 @@
"p-is-promise": "^2.0.0"
}
},
"memoizerific": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz",
"integrity": "sha1-fIekZGREwy11Q4VwkF8tvRsagFo=",
"requires": {
"map-or-similar": "^1.5.0"
}
},
"memory-fs": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
@@ -9586,6 +9798,16 @@
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz",
"integrity": "sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY="
},
"mini-create-react-context": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz",
"integrity": "sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw==",
"requires": {
"@babel/runtime": "^7.4.0",
"gud": "^1.0.0",
"tiny-warning": "^1.0.2"
}
},
"mini-css-extract-plugin": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz",
@@ -9628,9 +9850,9 @@
}
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"minipass": {
"version": "3.1.1",
@@ -9717,18 +9939,11 @@
}
},
"mkdirp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "0.0.8"
},
"dependencies": {
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
}
"minimist": "^1.2.5"
}
},
"moment": {
@@ -11693,6 +11908,21 @@
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
},
"prettier": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.4.tgz",
"integrity": "sha512-SVJIQ51spzFDvh4fIbCLvciiDMCrRhlN3mbZvv/+ycjvmF5E73bKdGfU8QDLNmjYJf+lsGnDBC4UUnvTe5OO0w==",
"dev": true
},
"prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"requires": {
"fast-diff": "^1.1.2"
}
},
"pretty-bytes": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.3.0.tgz",
@@ -12269,6 +12499,52 @@
"react-tween-state": "^0.1.5"
}
},
"react-router": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz",
"integrity": "sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A==",
"requires": {
"@babel/runtime": "^7.1.2",
"history": "^4.9.0",
"hoist-non-react-statics": "^3.1.0",
"loose-envify": "^1.3.1",
"mini-create-react-context": "^0.3.0",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.6.2",
"react-is": "^16.6.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"path-to-regexp": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
"integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
"requires": {
"isarray": "0.0.1"
}
}
}
},
"react-router-dom": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.1.2.tgz",
"integrity": "sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew==",
"requires": {
"@babel/runtime": "^7.1.2",
"history": "^4.9.0",
"loose-envify": "^1.3.1",
"prop-types": "^15.6.2",
"react-router": "5.1.2",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
}
},
"react-scripts": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.3.1.tgz",
@@ -12707,6 +12983,20 @@
"strip-indent": "^3.0.0"
}
},
"redux": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz",
"integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==",
"requires": {
"loose-envify": "^1.4.0",
"symbol-observable": "^1.2.0"
}
},
"redux-thunk": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
"integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw=="
},
"regenerate": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
@@ -12757,9 +13047,9 @@
}
},
"regexpp": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz",
"integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g=="
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
"integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q=="
},
"regexpu-core": {
"version": "4.6.0",
@@ -12980,6 +13270,11 @@
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
"integrity": "sha1-six699nWiBvItuZTM17rywoYh0g="
},
"resolve-pathname": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
"integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
},
"resolve-url": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
@@ -14487,9 +14782,9 @@
}
},
"strip-json-comments": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz",
"integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw=="
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz",
"integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w=="
},
"style-loader": {
"version": "1.1.3",
@@ -14634,8 +14929,7 @@
"symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
"dev": true
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
},
"symbol-tree": {
"version": "3.2.4",
@@ -14891,6 +15185,16 @@
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
},
"tiny-invariant": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz",
"integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw=="
},
"tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
},
"tinycolor2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
@@ -15045,6 +15349,11 @@
"resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.5.tgz",
"integrity": "sha512-ti7OGMOUOzo66wLF3liskw6YQIaSsBgc4GOAlWRnIEj8htCxJUxskanMUoJOD6MDCRAXo36goXJZch+nOS0VMA=="
},
"ts-toolbelt": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.5.1.tgz",
"integrity": "sha512-zjnZ/vy1eUA0li3H0JXecl0R3jiP42snpLimsrppt9V3LLbM4NM4jMgjXQ4S67hvehq+r9CxpX4Wj6RnFRzReA=="
},
"tslib": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
@@ -15364,6 +15673,11 @@
"spdx-expression-parse": "^3.0.0"
}
},
"value-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -17427,17 +17741,6 @@
"requires": {
"buffer-crc32": "~0.2.3",
"fd-slicer": "~1.1.0"
},
"dependencies": {
"fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
"integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
"dev": true,
"requires": {
"pend": "~1.2.0"
}
}
}
},
"zscroller": {
+73 -64
View File
@@ -1,67 +1,76 @@
{
"name": "hdhs",
"version": "0.1.0",
"private": true,
"dependencies": {
"@ant-design/icons": "^4.0.5",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.4.0",
"@testing-library/user-event": "^7.2.1",
"@types/jest": "^24.9.1",
"@types/node": "^12.12.26",
"@types/react": "^16.9.19",
"@types/react-dom": "^16.9.5",
"antd-mobile": "^2.3.1",
"node-sass": "^4.13.1",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-scripts": "3.3.1",
"typescript": "^3.7.5"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"cy:open": "cypress open",
"cy:run": "cypress run",
"test:cov": "react-scripts test --coverage --watchAll=false",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"jest": {
"coverageThreshold": {
"global": {
"statements": 90
}
"name": "hdhs",
"version": "0.1.0",
"private": true,
"dependencies": {
"@ant-design/icons": "^4.0.5",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.4.0",
"@testing-library/user-event": "^7.2.1",
"@types/jest": "^24.9.1",
"@types/node": "^12.12.26",
"@types/react": "^16.9.19",
"@types/react-dom": "^16.9.5",
"@types/react-router-dom": "^5.1.4",
"antd-mobile": "^2.3.1",
"easy-peasy": "^3.3.0",
"node-sass": "^4.13.1",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-router-dom": "^5.1.2",
"react-scripts": "3.3.1",
"typescript": "^3.7.5"
},
"coverageReporters": [
"lcov"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@cypress/webpack-preprocessor": "^4.1.3",
"@testing-library/react-hooks": "^3.2.1",
"awesome-typescript-loader": "^5.2.1",
"babel-loader": "^8.1.0",
"babel-plugin-import": "^1.13.0",
"cypress": "^4.2.0",
"react-test-renderer": "^16.13.1",
"svg-url-loader": "^5.0.0",
"ts-loader": "^6.2.2",
"webpack": "^4.42.1"
}
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"cy:open": "cypress open",
"cy:run": "cypress run",
"lint": "tsc --noEmit && eslint 'src/*.{js,ts,tsx}' --fix",
"test:cov": "react-scripts test --coverage --watchAll=false"
},
"eslintConfig": {
"extends": "react-app"
},
"jest": {
"coverageThreshold": {
"global": {
"statements": 90
}
},
"coverageReporters": [
"lcov"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@cypress/webpack-preprocessor": "^4.1.3",
"@testing-library/react-hooks": "^3.2.1",
"@typescript-eslint/eslint-plugin": "^2.28.0",
"@typescript-eslint/parser": "^2.28.0",
"awesome-typescript-loader": "^5.2.1",
"babel-loader": "^8.1.0",
"babel-plugin-import": "^1.13.0",
"cypress": "^4.2.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.10.1",
"eslint-plugin-prettier": "^3.1.3",
"prettier": "^2.0.4",
"react-test-renderer": "^16.13.1",
"svg-url-loader": "^5.0.0",
"ts-loader": "^6.2.2",
"webpack": "^4.42.1"
}
}
+7 -7
View File
@@ -1,9 +1,9 @@
import React from 'react';
import { render } from '@testing-library/react';
import App from './App';
import React from 'react'
import { render } from '@testing-library/react'
import App from './App'
test('renders learn react link', () => {
const { getByText } = render(<App />);
const linkElement = getByText(/Login/i);
expect(linkElement).toBeInTheDocument();
});
const { getByText } = render(<App />)
const linkElement = getByText(/Login/i)
expect(linkElement).toBeInTheDocument()
})
+45 -12
View File
@@ -1,15 +1,48 @@
import React from "react";
import "./scss/App.scss";
import "./antd-mobile.css";
import React from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import Login from "./components/Login";
import { StoreProvider } from 'easy-peasy'
import store from './state/store'
const App = () => {
return (
<>
<Login />
</>
);
};
import Login from './components/Login'
import LandingPage from './components/LandingPage'
import Wrapper from './Wrapper'
import PatientInfoForm from './components/PatientInfoForm'
export default App;
import './scss/App.scss'
import './antd-mobile.css'
import DebugShowEasyPeasy from './components/DebugShowEasyPeasy'
const App: React.FunctionComponent = () => {
return (
<StoreProvider store={store}>
<Router>
<Switch>
<Route path="/login">
<Login />
</Route>
<Route path="/landing">
<LandingPage />
</Route>
<Route path="/patientinfo">
<Wrapper
navTitle="Patient Information"
leftArrowText="Home"
leftArrowRoute="/"
>
<PatientInfoForm />
</Wrapper>
</Route>
<Route path="/easy-peasy">
<DebugShowEasyPeasy />
</Route>
<Route path="/">
<Login />
</Route>
</Switch>
</Router>
</StoreProvider>
)
}
export default App
+75
View File
@@ -0,0 +1,75 @@
import React from 'react'
import { render, queryByAttribute, fireEvent } from '@testing-library/react'
import { useHistory } from 'react-router-dom'
import Wrapper from './Wrapper'
const mockHistoryPush = jest.fn()
jest.mock('react-router-dom', () => ({
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
useHistory: () => ({
push: mockHistoryPush,
}),
}))
test("renders it's inner contents", () => {
const dom = render(
<Wrapper leftArrowText="back">
<div>Hello!</div>
</Wrapper>,
)
const textContents = dom.getByText('Hello!')
expect(textContents).toBeInTheDocument()
})
test('renders navBar when given navTitle', () => {
const dom = render(
<Wrapper navTitle="CHI">
<div>Hello!</div>
</Wrapper>,
)
const getById = queryByAttribute.bind(null, 'id')
const renderedNavBar = getById(dom.container, 'nav-bar')
const navTitle = dom.getByText('CHI')
expect(renderedNavBar).toBeTruthy()
expect(navTitle).toBeInTheDocument()
})
test('renders no navBar when not given props', () => {
const dom = render(
<Wrapper>
<div>Hello!</div>
</Wrapper>,
)
const getById = queryByAttribute.bind(null, 'id')
const renderedNavBar = getById(dom.container, 'nav-bar')
expect(renderedNavBar).toBeFalsy()
})
test('renders leftArrowText', () => {
const dom = render(
<Wrapper leftArrowText="back">
<div>Hello!</div>
</Wrapper>,
)
const leftArrowText = dom.getByText('back')
expect(leftArrowText).toBeInTheDocument()
})
test('redirects on left arrow click ', () => {
const dom = render(
<Wrapper leftArrowText="back" leftArrowRoute="/">
<div>Hello!</div>
</Wrapper>,
)
const leftArrowText = dom.getByText('back')
fireEvent.click(leftArrowText)
expect(useHistory().push).toBeCalledWith('/')
})
+44
View File
@@ -0,0 +1,44 @@
import React from 'react'
import { NavBar, Icon } from 'antd-mobile'
import { useHistory } from 'react-router-dom'
import './scss/Wrapper.scss'
type WrapperProps = {
navTitle?: string
leftArrowText?: string
leftArrowRoute?: string
children: React.ReactNode
}
const Wrapper: React.FunctionComponent<WrapperProps> = (
props: WrapperProps,
) => {
const { navTitle, leftArrowText, leftArrowRoute } = props
const hist = useHistory()
const needNavBar = navTitle || leftArrowText || leftArrowRoute
const leftContentArr = leftArrowText
? [<Icon key={1} type="left" />, `${leftArrowText}`]
: []
const leftRoute = leftArrowRoute ? `${leftArrowRoute}` : '/'
return (
<div id="primary-wrapper">
<div id="wrapper-contents">
{needNavBar && (
<NavBar
id="nav-bar"
mode="light"
onLeftClick={(): void => hist.push(leftRoute)}
leftContent={leftContentArr}
>
{`${navTitle}`}
</NavBar>
)}
{props.children}
</div>
</div>
)
}
export default Wrapper
+5949 -5380
View File
File diff suppressed because it is too large. Load diff
+7875 -2
View File
File diff suppressed because it is too large. Load diff
+16
View File
@@ -0,0 +1,16 @@
/* istanbul ignore file */
// This component is a debug utility to show
// all contents of easy-peasy when rendered
import React, { ReactElement } from 'react'
import { useStoreState } from '../state/storeIndex'
export default function DebugShowEasyPeasy(): ReactElement {
const patientInfoFormData = useStoreState(
(state) => state.patientInfoFormModel.fields,
)
console.log(patientInfoFormData)
return <div>patientInfoFormData: {JSON.stringify(patientInfoFormData)}</div>
}
+10 -10
View File
@@ -1,11 +1,11 @@
import React from "react";
import { render } from "@testing-library/react";
import LandingPage from "./LandingPage";
import React from 'react'
import { render } from '@testing-library/react'
import LandingPage from './LandingPage'
test("renders learn react link", () => {
const { getByText } = render(<LandingPage />);
const lookupButtonElement = getByText(/Patient Lookup/i);
const newPatientButtonElement = getByText(/New Patient/i);
expect(lookupButtonElement).toBeInTheDocument();
expect(newPatientButtonElement).toBeInTheDocument();
});
test('renders learn react link', () => {
const { getByText } = render(<LandingPage />)
const lookupButtonElement = getByText(/Patient Lookup/i)
const newPatientButtonElement = getByText(/New Patient/i)
expect(lookupButtonElement).toBeInTheDocument()
expect(newPatientButtonElement).toBeInTheDocument()
})
+30 -29
View File
@@ -1,34 +1,35 @@
import React, { ReactElement } from "react";
import { WhiteSpace, Button } from "antd-mobile";
import Icon, { UserAddOutlined } from "@ant-design/icons";
import React, { ReactElement } from 'react'
import { WhiteSpace, Button } from 'antd-mobile'
import { UserAddOutlined } from '@ant-design/icons'
import "../scss/LandingPage.scss";
import '../scss/LandingPage.scss'
export default function LandingPage(): ReactElement {
/* istanbul ignore next */
const patientLookup = () => {
console.log("Clicked Patient Lookup");
};
/* istanbul ignore next */
// TODO: these onClick functions could be mocked in a unit test
const patientLookup = () => {
console.log('Clicked Patient Lookup')
}
/* istanbul ignore next */
const newPatient = () => {
console.log("Clicked New Patient");
};
return (
<div id="landing-wrapper">
<div className="button-flex">
<Button onClick={patientLookup} icon="search">
Patient Lookup
</Button>
<WhiteSpace />
</div>
<br />
<div className="button-flex">
<Button icon={<UserAddOutlined />} onClick={newPatient}>
New Patient
</Button>
<WhiteSpace />
</div>
</div>
);
/* istanbul ignore next */
const newPatient = () => {
console.log('Clicked New Patient')
}
return (
<div id="landing-wrapper">
<div className="button-flex">
<Button onClick={patientLookup} icon="search">
Patient Lookup
</Button>
<WhiteSpace />
</div>
<br />
<div className="button-flex">
<Button icon={<UserAddOutlined />} onClick={newPatient}>
New Patient
</Button>
<WhiteSpace />
</div>
</div>
)
}
+21 -23
View File
@@ -1,31 +1,29 @@
import React, { ReactElement } from 'react'
import useApi from '../hooks/useApi'
import {InputItem, Button} from 'antd-mobile'
import { InputItem, Button } from 'antd-mobile'
import '../scss/Login.scss'
export default function Login(): ReactElement {
const {response, error, isLoading} = useApi("")
const { response, isLoading } = useApi('')
return (
<>
<div id="loginWrapper">
<div id="loginGrid">
<div id="iconCard">
<div id="imgWrap">
<img src="/logoIcon512.jpg" alt="CHI Logo"></img>
</div>
return (
<>
<div id="loginWrapper">
<div id="loginGrid">
<div id="iconCard">
<div id="imgWrap">
<img src="/logoIcon512.jpg" alt="CHI Logo"></img>
</div>
</div>
<InputItem placeholder="username" />
<InputItem placeholder="password" type="password" />
<div id="buttonFlex">
<Button>Login</Button>
</div>
</div>
</div>
<InputItem placeholder="username"/>
<InputItem placeholder="password" type="password"/>
<div id="buttonFlex">
<Button>Login</Button>
</div>
</div>
</div>
<p>Loading API Fetch: {isLoading.toString()}</p>
{response &&
<p>API Fetch Result: {JSON.stringify(response)}</p>
}
</>
)
<p>Loading API Fetch: {isLoading.toString()}</p>
{response && <p>API Fetch Result: {JSON.stringify(response)}</p>}
</>
)
}
+147
View File
@@ -0,0 +1,147 @@
import React from 'react'
import { render, fireEvent, prettyDOM } from '@testing-library/react'
import PatientInfoForm from './PatientInfoForm'
import { StoreProvider } from 'easy-peasy'
import store from '../state/store'
test('female-only div is only displayed if the sex is selected as female', async () => {
const femaleOnlyFormFields = ['Pregnant?', 'LMP', 'Wants Planning?']
const dom = render(
<StoreProvider store={store}>
<PatientInfoForm />
</StoreProvider>,
)
// ensure the female-only div is not displayed
femaleOnlyFormFields.forEach((fieldText) => {
expect(dom.queryByText(fieldText)).toBeNull()
})
// click the radio button for 'female'
const femaleButton = dom.getByTestId('female-button')
fireEvent.click(femaleButton)
femaleOnlyFormFields.forEach((fieldText) => {
expect(dom.queryByText(fieldText)).toBeTruthy()
})
})
test('sets visit input', () => {
const dom = render(
<StoreProvider store={store}>
<PatientInfoForm />
</StoreProvider>,
)
const firstVisitYesInput = dom.queryByTestId('first-visit-Yes')
const checkBox = firstVisitYesInput.getElementsByTagName('span')[0]
expect(checkBox).not.toHaveClass('am-radio-checked')
fireEvent.click(firstVisitYesInput)
expect(checkBox).toHaveClass('am-radio-checked')
})
test('sets pregnant input', () => {
const dom = render(
<StoreProvider store={store}>
<PatientInfoForm />
</StoreProvider>,
)
const femaleButton = dom.getByTestId('female-button')
fireEvent.click(femaleButton)
const pregnantYesInput = dom.queryByTestId('pregnant-Yes')
const checkBox = pregnantYesInput.getElementsByTagName('span')[0]
expect(checkBox).not.toHaveClass('am-radio-checked')
fireEvent.click(pregnantYesInput)
expect(checkBox).toHaveClass('am-radio-checked')
})
test('sets planning input', () => {
const dom = render(
<StoreProvider store={store}>
<PatientInfoForm />
</StoreProvider>,
)
const femaleButton = dom.getByTestId('female-button')
fireEvent.click(femaleButton)
const planningYesInput = dom.queryByTestId('planning-Yes')
const checkBox = planningYesInput.getElementsByTagName('span')[0]
expect(checkBox).not.toHaveClass('am-radio-checked')
fireEvent.click(planningYesInput)
expect(checkBox).toHaveClass('am-radio-checked')
})
test('validates blood pressure input', () => {
const dom = render(
<StoreProvider store={store}>
<PatientInfoForm />
</StoreProvider>,
)
const bpInput = dom.getByPlaceholderText(
'Blood Pressure',
) as HTMLInputElement
fireEvent.change(bpInput, { target: { value: '130/85' } })
expect(bpInput.value).toBe('130/85')
fireEvent.change(bpInput, { target: { value: '' } })
fireEvent.change(bpInput, { target: { value: 'abcd' } })
expect(bpInput.value).toBe('') // Empty String
})
test('sets albendazole input', () => {
const dom = render(
<StoreProvider store={store}>
<PatientInfoForm />
</StoreProvider>,
)
const albendazoleYesInput = dom.queryByTestId('albendazole-Yes')
const checkBox = albendazoleYesInput.getElementsByTagName('span')[0]
expect(checkBox).not.toHaveClass('am-radio-checked')
fireEvent.click(albendazoleYesInput)
expect(checkBox).toHaveClass('am-radio-checked')
})
test('renders the Patient Info Form fields', () => {
const formFields = [
'First Name',
'First Name',
'Last Name',
'Last Name',
'Sex',
'Birth',
'Age',
'Phone',
'Town',
'Visit',
'Nickname',
'BP',
'Temp',
'Pulse',
'Weight',
'Height',
'Z-Score',
'Albendazole',
'Allergies',
'Medicines',
'Chief Complaint',
'History',
'Exam',
]
const { getByText } = render(
<StoreProvider store={store}>
<PatientInfoForm />
</StoreProvider>,
)
formFields.forEach((fieldText) => {
const re = new RegExp(fieldText, 'gi')
expect(getByText(re)).toBeInTheDocument()
})
})
+388
View File
@@ -0,0 +1,388 @@
import React, { ReactElement, useState } from 'react'
import { useStoreActions } from '../state/storeIndex'
import { SEX } from '../state/enums'
import {
InputItem,
List,
TextareaItem,
Button,
Radio,
WhiteSpace,
} from 'antd-mobile'
import '../scss/PatientInfoForm.scss'
const RadioItem = Radio.RadioItem
export default function Form(): ReactElement {
// State Hooks for Radio Buttons
// useState returns a value, and a function to set that value
const [sex, setSex] = useState(null as number) // Default: null
const [albendazole, setAlbendazole] = useState(0) // Default: No
const [firstVisit, setFirstVisit] = useState(0) // Default: No
const [pregnant, setPregnant] = useState(0) // Default: No
const [planning, setPlanning] = useState(0) // Default: No
const [bloodPressureValue, setBloodPressureValue] = useState('')
const updateCentralState = useStoreActions(
(actions) => actions.patientInfoFormModel.updateFields,
)
// Data for Radio Buttons
const sexValues = [
{ value: 0, label: 'Male' },
{ value: 1, label: 'Female' },
]
const radioYesNo = [
{ value: 0, label: 'No' },
{ value: 1, label: 'Yes' },
]
/* istanbul ignore next */
const onSubmit = () => {
const firstNameField: HTMLInputElement = document.getElementById(
'firstname',
) as HTMLInputElement
const lastNameField: HTMLInputElement = document.getElementById(
'lastname',
) as HTMLInputElement
const nicknameField: HTMLInputElement = document.getElementById(
'nickname',
) as HTMLInputElement
const birthdateField: HTMLInputElement = document.getElementById(
'birthdate',
) as HTMLInputElement
const age: HTMLInputElement = document.getElementById(
'age',
) as HTMLInputElement
const phone: HTMLInputElement = document.getElementById(
'phone',
) as HTMLInputElement
const town: HTMLInputElement = document.getElementById(
'town',
) as HTMLInputElement
const gravida: HTMLInputElement = document.getElementById(
'gravida',
) as HTMLInputElement
const para: HTMLInputElement = document.getElementById(
'para',
) as HTMLInputElement
const abortus: HTMLInputElement = document.getElementById(
'abortus',
) as HTMLInputElement
const lmp: HTMLInputElement = document.getElementById(
'lmp',
) as HTMLInputElement
const weight: HTMLInputElement = document.getElementById(
'weight',
) as HTMLInputElement
const height: HTMLInputElement = document.getElementById(
'height',
) as HTMLInputElement
const temp: HTMLInputElement = document.getElementById(
'temp',
) as HTMLInputElement
const pulse: HTMLInputElement = document.getElementById(
'pulse',
) as HTMLInputElement
const zScore: HTMLInputElement = document.getElementById(
'z-score',
) as HTMLInputElement
const allergies: HTMLInputElement = document.getElementById(
'allergies',
) as HTMLInputElement
const medicines: HTMLInputElement = document.getElementById(
'medicines',
) as HTMLInputElement
const complaintField1: HTMLInputElement = document.getElementById(
'complaint1',
) as HTMLInputElement
const complaintField2: HTMLInputElement = document.getElementById(
'complaint2',
) as HTMLInputElement
const complaintField3: HTMLInputElement = document.getElementById(
'complaint3',
) as HTMLInputElement
const history: HTMLInputElement = document.getElementById(
'history',
) as HTMLInputElement
const exam: HTMLInputElement = document.getElementById(
'exam',
) as HTMLInputElement
updateCentralState({
firstName: firstNameField.value,
lastName: lastNameField.value,
nickname: nicknameField.value,
sex: sex ? SEX.female : SEX.male,
birthDate: new Date(birthdateField.value),
age: parseInt(age.value),
phone: phone.value,
town: town.value,
firstVisit: firstVisit,
pregnant: pregnant,
G: sex ? parseInt(gravida.value) : null,
P: sex ? parseInt(para.value) : null,
A: sex ? parseInt(abortus.value) : null,
LMP: sex ? new Date(lmp.value) : null,
wantsPlanning: planning,
weight: parseFloat(weight.value),
height: parseFloat(height.value),
bloodPressure: bloodPressureValue,
temperature: parseFloat(temp.value),
pulse: parseFloat(pulse.value),
zScore: parseFloat(zScore.value),
albendazole: albendazole,
allergies: allergies.value,
medicines: medicines.value,
chiefComplaint: [
complaintField1.value,
complaintField2.value,
complaintField3.value,
],
history: history.value,
exam: exam.value,
})
}
const validateAndSetBloodPressure = (value: string) => {
const newChar = value.slice(-1)
if (newChar.match(/[0-9]|\/|^$/) && value.length < 8) {
setBloodPressureValue(value)
}
}
return (
<div id="patient-info-wrapper">
<TextareaItem
title="First Name"
placeholder="First Name"
autoHeight
id="firstname"
/>
<TextareaItem
title="Last Name"
placeholder="Last Name"
id="lastname"
autoHeight
/>
<TextareaItem
title="Nickname"
placeholder="Nickname"
id="nickname"
autoHeight
/>
<List renderHeader={() => 'Sex'}>
{sexValues.map((i) => (
<RadioItem
data-testid={`${i.label.toLowerCase()}-button`}
id={`${i.label.toLowerCase()}-button`}
key={i.value}
name="sex"
checked={sex === i.value}
onClick={() => setSex(i.value)}
>
{i.label}
</RadioItem>
))}
</List>
<WhiteSpace />
<InputItem placeholder="YYYY-MM-DD" id="birthdate" maxLength={10}>
Birth Date
</InputItem>
<InputItem type="number" placeholder="Age" id="age" maxLength={3}>
Age{' '}
</InputItem>
<InputItem type="number" placeholder="Phone" id="phone">
Phone
</InputItem>
<TextareaItem
title="Town"
placeholder="Town"
id="town"
autoHeight
/>
<List renderHeader={() => 'First Visit?'}>
{radioYesNo.map((i) => (
<RadioItem
key={i.value}
data-testid={`first-visit-${i.label}`}
name="visit"
checked={firstVisit === i.value}
onClick={() => setFirstVisit(i.value)}
>
{i.label}
</RadioItem>
))}
</List>
{/* Only display the 'female-only' div if the patient is female */}
{sex ? (
<div id="female-only">
<WhiteSpace />
<List renderHeader={() => 'Pregnant?'}>
{radioYesNo.map((i) => (
<RadioItem
data-testid={`pregnant-${i.label}`}
key={i.value}
name="pregnant"
checked={pregnant === i.value}
onClick={() => setPregnant(i.value)}
>
{i.label}
</RadioItem>
))}
</List>
<InputItem
title="G"
type="number"
id="gravida"
placeholder="0"
>
G
</InputItem>
<InputItem
title="P"
type="number"
id="para"
placeholder="0"
>
P
</InputItem>
<InputItem
title="A"
type="number"
id="abortus"
placeholder="0"
>
A
</InputItem>
<InputItem placeholder="YYYY-MM-DD" id="lmp" maxLength={10}>
LMP
</InputItem>
<List renderHeader={() => 'Wants Planning?'}>
{radioYesNo.map((i) => (
<RadioItem
data-testid={`planning-${i.label}`}
key={i.value}
name="planning"
checked={planning === i.value}
onClick={() => setPlanning(i.value)}
>
{i.label}
</RadioItem>
))}
</List>
</div>
) : null}
<WhiteSpace />
<InputItem
type="number"
placeholder="Weight"
id="weight"
maxLength={3}
>
Weight (kg)
</InputItem>
<InputItem
type="number"
placeholder="Height"
id="height"
maxLength={3}
>
Height (cm)
</InputItem>
<InputItem
placeholder="Blood Pressure"
id="bp"
value={bloodPressureValue}
onChange={validateAndSetBloodPressure}
>
BP
</InputItem>
<InputItem type="number" placeholder="Temp" id="temp" maxLength={3}>
Temp (F)
</InputItem>
<InputItem
type="number"
placeholder="Pulse"
id="pulse"
maxLength={3}
>
Pulse
</InputItem>
<InputItem type="number" placeholder="Z-Score" id="z-score">
Z-Score
</InputItem>
<List renderHeader={() => 'Albendazole?'}>
{radioYesNo.map((i) => (
<RadioItem
data-testid={`albendazole-${i.label}`}
key={i.value}
checked={albendazole === i.value}
onClick={() => setAlbendazole(i.value)}
>
{i.label}
</RadioItem>
))}
</List>
<List renderHeader={() => 'Allergies'}>
<TextareaItem autoHeight id="allergies" />
</List>
<List renderHeader={() => 'Medicines'}>
<TextareaItem autoHeight id="medicines" />
</List>
<List renderHeader={() => 'Chief Complaint'}>
<TextareaItem
placeholder="1."
// autoHeight
autoHeight
id="complaint1"
/>
<TextareaItem placeholder="2." autoHeight id="complaint2" />
<TextareaItem placeholder="3." autoHeight id="complaint3" />
</List>
<List renderHeader={() => 'History'}>
<TextareaItem autoHeight id="history" />
</List>
<List renderHeader={() => 'Exam'}>
<TextareaItem autoHeight id="exam" />
</List>
<WhiteSpace />
<div className="submit-button">
<List>
<Button data-testid="submit-button" onClick={onSubmit}>
Submit
</Button>
</List>
</div>
</div>
)
}
+15 -3
View File
@@ -2,8 +2,8 @@ import { renderHook } from '@testing-library/react-hooks'
import useApi from './useApi'
test('should return loading and then a response', async () => {
const { result, waitForNextUpdate } = renderHook(() => useApi(""))
const { result, waitForNextUpdate } = renderHook(() => useApi(''))
expect(result.current.response).toBeNull()
expect(result.current.error).toBeNull()
expect(result.current.isLoading).toBeTruthy()
@@ -13,4 +13,16 @@ test('should return loading and then a response', async () => {
expect(result.current.response).toBeTruthy()
expect(result.current.error).toBeNull()
expect(result.current.isLoading).toBeFalsy()
})
})
test('should return an error on a non existent route', async () => {
const { result, waitForNextUpdate } = renderHook(() => useApi('/404'))
expect(result.current.response).toBeNull()
expect(result.current.error).toBeNull()
await waitForNextUpdate()
expect(result.current.response).toBeFalsy()
expect(result.current.error).toBeTruthy()
})
+30 -30
View File
@@ -1,35 +1,35 @@
import React from 'react'
const useApi = (route: string) => {
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
const [isLoading, setIsLoading] = React.useState(false);
/* istanbul ignore next */
const getUrl = () => {
if (process.env.environment === "docker"){
return "http://api:4000/"
}
else {
return "http://localhost:4000/"
}
}
const [response, setResponse] = React.useState(null)
const [error, setError] = React.useState(null)
const [isLoading, setIsLoading] = React.useState(false)
React.useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
try {
const res = await fetch(getUrl() + route);
const json = await res.json();
setResponse(json);
setIsLoading(false)
} catch (error) {
setError(error);
}
};
fetchData();
}, []);
return { response, error, isLoading };
};
/* istanbul ignore next */
const getUrl = () => {
if (process.env.environment === 'docker') {
return 'http://api:4000/'
} else {
return 'http://localhost:4000/'
}
}
export default useApi
React.useEffect(() => {
const fetchData = async () => {
setIsLoading(true)
try {
const res = await fetch(getUrl() + route)
const json = await res.json()
setResponse(json)
setIsLoading(false)
} catch (error) {
setError(error)
}
}
fetchData()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return { response, error, isLoading }
}
export default useApi
+7 -7
View File
@@ -1,13 +1,13 @@
/* istanbul ignore file */
import React from 'react';
import ReactDOM from 'react-dom';
import './scss/index.scss';
import App from './App';
import * as serviceWorker from './serviceWorker';
import React from 'react'
import ReactDOM from 'react-dom'
import './scss/index.scss'
import App from './App'
import * as serviceWorker from './serviceWorker'
ReactDOM.render(<App />, document.getElementById('root'));
ReactDOM.render(<App />, document.getElementById('root'))
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.register();
serviceWorker.register()
+10 -2
View File
@@ -1,7 +1,15 @@
.am-button:hover {
cursor: pointer;
cursor: pointer;
}
#root {
height: 100vh;
height: 100vh;
}
.am-input-label {
padding-right: 0.5em !important;
}
.am-textarea-label {
padding-right: 0.5em !important;
}
+13 -15
View File
@@ -1,21 +1,19 @@
.button-flex {
animation-name: fade-in;
animation-duration: 2s;
display: flex;
justify-content: center;
margin: 2em 0;
.am-button {
background-color: #9bd7ff;
width: 40%;
&.am-button-active {
background-color: #ccebff;
display: flex;
justify-content: center;
margin: 2em 0;
.am-button {
background-color: #9bd7ff;
width: 40%;
&.am-button-active {
background-color: #ccebff;
}
}
}
}
#landing-wrapper {
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
}
+69 -61
View File
@@ -2,70 +2,78 @@
// @include media("<desktop") {
#loginWrapper {
display: flex;
flex-direction: column;
justify-content: space-around;
height: 100%;
padding: 0 2rem;
#loginGrid {
display: grid;
align-items: center;
justify-content: center;
height: 45%;
#iconCard {
animation-name: fade-in-from-up;
animation-duration: 1.5s;
justify-content: center;
align-items: center;
flex-direction: row;
#imgWrap {
width: 28%;
margin: auto;
padding: .2rem;
background-color: #eee;
border-radius: 5px;
img {
display: block;
width: 100%;
max-width: 512px;
height: auto;
margin: auto;
display: flex;
flex-direction: column;
justify-content: space-around;
height: 100%;
padding: 0 2rem;
#loginGrid {
display: grid;
align-items: center;
justify-content: center;
height: 45%;
#iconCard {
animation-name: fade-in-from-up;
animation-duration: 1.5s;
justify-content: center;
align-items: center;
flex-direction: row;
#imgWrap {
width: 28%;
margin: auto;
padding: 0.2rem;
background-color: #eee;
border-radius: 5px;
img {
display: block;
width: 100%;
max-width: 512px;
height: auto;
margin: auto;
}
}
}
}
}
input {
animation-name: fade-in;
animation-duration: 2s;
}
#buttonFlex{
animation-name: fade-in;
animation-duration: 2s;
display: flex;
justify-content: center;
.am-button {
background-color: #9BD7FF;
width: 40%;
&.am-button-active {
background-color: #ccebff;
input {
animation-name: fade-in;
animation-duration: 2s;
}
#buttonFlex {
animation-name: fade-in;
animation-duration: 2s;
display: flex;
justify-content: center;
.am-button {
background-color: #9bd7ff;
width: 40%;
&.am-button-active {
background-color: #ccebff;
}
}
}
}
$animationDelay: 1;
@for $i from 1 through 15 {
.loginGrid :nth-child(#{$i}) {
animation-delay: #{0.3+ ($i)/30}s;
}
}
}
}
}
$animationDelay: 1;
@for $i from 1 through 15 {
.loginGrid :nth-child(#{$i}) {
animation-delay: #{0.3+($i)/30}s;
}
}
@keyframes fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fade-in {
0% { opacity: 0; }
100% { opacity: 1; }
@keyframes fade-in-from-up {
0% {
opacity: 0;
transform: translateY(-100px);
}
100% {
opacity: 1;
}
}
}
@keyframes fade-in-from-up {
0% { opacity: 0; transform: translateY(-100px)}
100% { opacity: 1; }
}
+17
View File
@@ -0,0 +1,17 @@
#patient-info-wrapper {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
.submit-button {
display: flex;
justify-content: center;
padding: 2em 0;
width: 100%;
.am-list {
min-width: 8em;
width: 40%;
max-width: 20em;
}
}
}
+12
View File
@@ -0,0 +1,12 @@
#primary-wrapper {
width: 100%;
height: 100vh;
display: flex;
justify-content: space-around;
#wrapper-contents {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2),
0 6px 20px 0 rgba(0, 0, 0, 0.19);
width: 55em;
overflow: scroll;
}
}
+200 -170
View File
@@ -22,7 +22,6 @@
/// @access public
////
///
/// Creates a list of global breakpoints
///
@@ -30,12 +29,11 @@
/// $breakpoints: ('phone': 320px);
///
$breakpoints: (
'phone': 320px,
'tablet': 768px,
'desktop': 1024px
'phone': 320px,
'tablet': 768px,
'desktop': 1024px,
) !default;
///
/// Creates a list of static expressions or media types
///
@@ -48,16 +46,17 @@ $breakpoints: (
/// );
///
$media-expressions: (
'screen': 'screen',
'print': 'print',
'handheld': 'handheld',
'landscape': '(orientation: landscape)',
'portrait': '(orientation: portrait)',
'retina2x': '(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi), (min-resolution: 2dppx)',
'retina3x': '(-webkit-min-device-pixel-ratio: 3), (min-resolution: 350dpi), (min-resolution: 3dppx)'
'screen': 'screen',
'print': 'print',
'handheld': 'handheld',
'landscape': '(orientation: landscape)',
'portrait': '(orientation: portrait)',
'retina2x':
'(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi), (min-resolution: 2dppx)',
'retina3x':
'(-webkit-min-device-pixel-ratio: 3), (min-resolution: 350dpi), (min-resolution: 3dppx)',
) !default;
///
/// Defines a number to be added or subtracted from each unit when declaring breakpoints with exclusive intervals
///
@@ -80,10 +79,10 @@ $media-expressions: (
/// @media (min-width: 2.1rem) {}
///
$unit-intervals: (
'px': 1,
'em': 0.01,
'rem': 0.1,
'': 0
'px': 1,
'em': 0.01,
'rem': 0.1,
'': 0,
) !default;
///
@@ -175,7 +174,6 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @access private
////
///
/// Log a message either with `@error` if supported
/// else with `@warn`, using `feature-exists('at-error')`
@@ -184,17 +182,16 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @param {String} $message - Message to log
///
@function im-log($message) {
@if feature-exists('at-error') {
@error $message;
} @else {
@warn $message;
$_: noop();
}
@if feature-exists('at-error') {
@error $message;
} @else {
@warn $message;
$_: noop();
}
@return $message;
@return $message;
}
///
/// Wrapper mixin for the log function so it can be used with a more friendly
/// API than `@if im-log('..') {}` or `$_: im-log('..')`. Basically, use the function
@@ -204,15 +201,16 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @param {String} $message - Message to log
///
@mixin log($message) {
@if im-log($message) {}
@if im-log($message) {
}
}
///
/// Function with no `@return` called next to `@warn` in Sass 3.3
/// to trigger a compiling error and stop the process.
///
@function noop() {}
@function noop() {
}
///
/// Determines whether a list of conditions is intercepted by the static breakpoint.
@@ -222,28 +220,30 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @return {Boolean} - Returns true if the conditions are intercepted by the static breakpoint
///
@function im-intercepts-static-breakpoint($conditions...) {
$no-media-breakpoint-value: map-get($breakpoints, $im-no-media-breakpoint);
$no-media-breakpoint-value: map-get($breakpoints, $im-no-media-breakpoint);
@if not $no-media-breakpoint-value {
@if im-log('`#{$im-no-media-breakpoint}` is not a valid breakpoint.') {}
}
@each $condition in $conditions {
@if not map-has-key($media-expressions, $condition) {
$operator: get-expression-operator($condition);
$prefix: get-expression-prefix($operator);
$value: get-expression-value($condition, $operator);
@if ($prefix == 'max' and $value <= $no-media-breakpoint-value) or
($prefix == 'min' and $value > $no-media-breakpoint-value) {
@return false;
}
} @else if not index($im-no-media-expressions, $condition) {
@return false;
@if not $no-media-breakpoint-value {
@if im-log('`#{$im-no-media-breakpoint}` is not a valid breakpoint.') {
}
}
}
@return true;
@each $condition in $conditions {
@if not map-has-key($media-expressions, $condition) {
$operator: get-expression-operator($condition);
$prefix: get-expression-prefix($operator);
$value: get-expression-value($condition, $operator);
@if ($prefix == 'max' and $value <= $no-media-breakpoint-value) or
($prefix == 'min' and $value > $no-media-breakpoint-value)
{
@return false;
}
} @else if not index($im-no-media-expressions, $condition) {
@return false;
}
}
@return true;
}
////
@@ -252,7 +252,6 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @access private
////
///
/// Get operator of an expression
///
@@ -261,21 +260,20 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @return {String} - Any of `>=`, `>`, `<=`, `<`, `≥`, `≤`
///
@function get-expression-operator($expression) {
@each $operator in ('>=', '>', '<=', '<', '≥', '≤') {
@if str-index($expression, $operator) {
@return $operator;
@each $operator in ('>=', '>', '<=', '<', '≥', '≤') {
@if str-index($expression, $operator) {
@return $operator;
}
}
}
// It is not possible to include a mixin inside a function, so we have to
// rely on the `im-log(..)` function rather than the `log(..)` mixin. Because
// functions cannot be called anywhere in Sass, we need to hack the call in
// a dummy variable, such as `$_`. If anybody ever raise a scoping issue with
// Sass 3.3, change this line in `@if im-log(..) {}` instead.
$_: im-log('No operator found in `#{$expression}`.');
// It is not possible to include a mixin inside a function, so we have to
// rely on the `im-log(..)` function rather than the `log(..)` mixin. Because
// functions cannot be called anywhere in Sass, we need to hack the call in
// a dummy variable, such as `$_`. If anybody ever raise a scoping issue with
// Sass 3.3, change this line in `@if im-log(..) {}` instead.
$_: im-log('No operator found in `#{$expression}`.');
}
///
/// Get dimension of an expression, based on a found operator
///
@@ -285,18 +283,17 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @return {String} - `width` or `height` (or potentially anything else)
///
@function get-expression-dimension($expression, $operator) {
$operator-index: str-index($expression, $operator);
$parsed-dimension: str-slice($expression, 0, $operator-index - 1);
$dimension: 'width';
$operator-index: str-index($expression, $operator);
$parsed-dimension: str-slice($expression, 0, $operator-index - 1);
$dimension: 'width';
@if str-length($parsed-dimension) > 0 {
$dimension: $parsed-dimension;
}
@if str-length($parsed-dimension) > 0 {
$dimension: $parsed-dimension;
}
@return $dimension;
@return $dimension;
}
///
/// Get dimension prefix based on an operator
///
@@ -305,10 +302,9 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @return {String} - `min` or `max`
///
@function get-expression-prefix($operator) {
@return if(index(('<', '<=', '≤'), $operator), 'max', 'min');
@return if(index(('<', '<=', '≤'), $operator), 'max', 'min');
}
///
/// Get value of an expression, based on a found operator
///
@@ -318,36 +314,35 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @return {Number} - A numeric value
///
@function get-expression-value($expression, $operator) {
$operator-index: str-index($expression, $operator);
$value: str-slice($expression, $operator-index + str-length($operator));
$operator-index: str-index($expression, $operator);
$value: str-slice($expression, $operator-index + str-length($operator));
@if map-has-key($breakpoints, $value) {
$value: map-get($breakpoints, $value);
} @else {
$value: to-number($value);
}
@if map-has-key($breakpoints, $value) {
$value: map-get($breakpoints, $value);
} @else {
$value: to-number($value);
}
$interval: map-get($unit-intervals, unit($value));
$interval: map-get($unit-intervals, unit($value));
@if not $interval {
// It is not possible to include a mixin inside a function, so we have to
// rely on the `im-log(..)` function rather than the `log(..)` mixin. Because
// functions cannot be called anywhere in Sass, we need to hack the call in
// a dummy variable, such as `$_`. If anybody ever raise a scoping issue with
// Sass 3.3, change this line in `@if im-log(..) {}` instead.
$_: im-log('Unknown unit `#{unit($value)}`.');
}
@if not $interval {
// It is not possible to include a mixin inside a function, so we have to
// rely on the `im-log(..)` function rather than the `log(..)` mixin. Because
// functions cannot be called anywhere in Sass, we need to hack the call in
// a dummy variable, such as `$_`. If anybody ever raise a scoping issue with
// Sass 3.3, change this line in `@if im-log(..) {}` instead.
$_: im-log('Unknown unit `#{unit($value)}`.');
}
@if $operator == '>' {
$value: $value + $interval;
} @else if $operator == '<' {
$value: $value - $interval;
}
@if $operator == '>' {
$value: $value + $interval;
} @else if $operator == '<' {
$value: $value - $interval;
}
@return $value;
@return $value;
}
///
/// Parse an expression to return a valid media-query expression
///
@@ -356,18 +351,18 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @return {String} - Valid media query
///
@function parse-expression($expression) {
// If it is part of $media-expressions, it has no operator
// then there is no need to go any further, just return the value
@if map-has-key($media-expressions, $expression) {
@return map-get($media-expressions, $expression);
}
// If it is part of $media-expressions, it has no operator
// then there is no need to go any further, just return the value
@if map-has-key($media-expressions, $expression) {
@return map-get($media-expressions, $expression);
}
$operator: get-expression-operator($expression);
$dimension: get-expression-dimension($expression, $operator);
$prefix: get-expression-prefix($operator);
$value: get-expression-value($expression, $operator);
$operator: get-expression-operator($expression);
$dimension: get-expression-dimension($expression, $operator);
$prefix: get-expression-prefix($operator);
$value: get-expression-value($expression, $operator);
@return '(#{$prefix}-#{$dimension}: #{$value})';
@return '(#{$prefix}-#{$dimension}: #{$value})';
}
///
@@ -382,17 +377,17 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @return {List} Sliced list
///
@function slice($list, $start: 1, $end: length($list)) {
@if length($list) < 1 or $start > $end {
@return ();
}
@if length($list) < 1 or $start > $end {
@return ();
}
$result: ();
$result: ();
@for $i from $start through $end {
$result: append($result, nth($list, $i));
}
@for $i from $start through $end {
$result: append($result, nth($list, $i));
}
@return $result;
@return $result;
}
////
@@ -401,7 +396,6 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @access private
////
///
/// Casts a string into a number
///
@@ -410,44 +404,57 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @return {Number}
///
@function to-number($value) {
@if type-of($value) == 'number' {
@return $value;
} @else if type-of($value) != 'string' {
$_: im-log('Value for `to-number` should be a number or a string.');
}
$first-character: str-slice($value, 1, 1);
$result: 0;
$digits: 0;
$minus: ($first-character == '-');
$numbers: ('0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9);
// Remove +/- sign if present at first character
@if ($first-character == '+' or $first-character == '-') {
$value: str-slice($value, 2);
}
@for $i from 1 through str-length($value) {
$character: str-slice($value, $i, $i);
@if not (index(map-keys($numbers), $character) or $character == '.') {
@return to-length(if($minus, -$result, $result), str-slice($value, $i))
@if type-of($value) == 'number' {
@return $value;
} @else if type-of($value) != 'string' {
$_: im-log('Value for `to-number` should be a number or a string.');
}
@if $character == '.' {
$digits: 1;
} @else if $digits == 0 {
$result: $result * 10 + map-get($numbers, $character);
} @else {
$digits: $digits * 10;
$result: $result + map-get($numbers, $character) / $digits;
}
}
$first-character: str-slice($value, 1, 1);
$result: 0;
$digits: 0;
$minus: ($first-character == '-');
$numbers: (
'0': 0,
'1': 1,
'2': 2,
'3': 3,
'4': 4,
'5': 5,
'6': 6,
'7': 7,
'8': 8,
'9': 9,
);
@return if($minus, -$result, $result);
// Remove +/- sign if present at first character
@if ($first-character == '+' or $first-character == '-') {
$value: str-slice($value, 2);
}
@for $i from 1 through str-length($value) {
$character: str-slice($value, $i, $i);
@if not(index(map-keys($numbers), $character) or $character == '.') {
@return to-length(
if($minus, -$result, $result),
str-slice($value, $i)
);
}
@if $character == '.' {
$digits: 1;
} @else if $digits == 0 {
$result: $result * 10 + map-get($numbers, $character);
} @else {
$digits: $digits * 10;
$result: $result + map-get($numbers, $character) / $digits;
}
}
@return if($minus, -$result, $result);
}
///
/// Add `$unit` to `$value`
///
@@ -457,13 +464,29 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @return {Number} - `$value` expressed in `$unit`
///
@function to-length($value, $unit) {
$units: ('px': 1px, 'cm': 1cm, 'mm': 1mm, '%': 1%, 'ch': 1ch, 'pc': 1pc, 'in': 1in, 'em': 1em, 'rem': 1rem, 'pt': 1pt, 'ex': 1ex, 'vw': 1vw, 'vh': 1vh, 'vmin': 1vmin, 'vmax': 1vmax);
$units: (
'px': 1px,
'cm': 1cm,
'mm': 1mm,
'%': 1%,
'ch': 1ch,
'pc': 1pc,
'in': 1in,
'em': 1em,
'rem': 1rem,
'pt': 1pt,
'ex': 1ex,
'vw': 1vw,
'vh': 1vh,
'vmin': 1vmin,
'vmax': 1vmax,
);
@if not index(map-keys($units), $unit) {
$_: im-log('Invalid unit `#{$unit}`.');
}
@if not index(map-keys($units), $unit) {
$_: im-log('Invalid unit `#{$unit}`.');
}
@return $value * map-get($units, $unit);
@return $value * map-get($units, $unit);
}
///
@@ -505,19 +528,22 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// }
///
@mixin media-context($tweakpoints: (), $tweak-media-expressions: ()) {
// Save global configuration
$global-breakpoints: $breakpoints;
$global-media-expressions: $media-expressions;
// Save global configuration
$global-breakpoints: $breakpoints;
$global-media-expressions: $media-expressions;
// Update global configuration
$breakpoints: map-merge($breakpoints, $tweakpoints) !global;
$media-expressions: map-merge($media-expressions, $tweak-media-expressions) !global;
// Update global configuration
$breakpoints: map-merge($breakpoints, $tweakpoints) !global;
$media-expressions: map-merge(
$media-expressions,
$tweak-media-expressions
) !global;
@content;
@content;
// Restore global configuration
$breakpoints: $global-breakpoints !global;
$media-expressions: $global-media-expressions !global;
// Restore global configuration
$breakpoints: $global-breakpoints !global;
$media-expressions: $global-media-expressions !global;
}
////
@@ -526,7 +552,6 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @access public
////
///
/// Generates a media query based on a list of conditions
///
@@ -551,15 +576,20 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @include media('>=350px', '<tablet', 'retina3x') { }
///
@mixin media($conditions...) {
@if ($im-media-support and length($conditions) == 0) or
(not $im-media-support and im-intercepts-static-breakpoint($conditions...)) {
@content;
} @else if ($im-media-support and length($conditions) > 0) {
@media #{unquote(parse-expression(nth($conditions, 1)))} {
// Recursive call
@include media(slice($conditions, 2)...) {
@if ($im-media-support and length($conditions) == 0) or
(
not
$im-media-support and
im-intercepts-static-breakpoint($conditions...)
)
{
@content;
}
} @else if ($im-media-support and length($conditions) > 0) {
@media #{unquote(parse-expression(nth($conditions, 1)))} {
// Recursive call
@include media(slice($conditions, 2) ...) {
@content;
}
}
}
}
}
+12 -12
View File
@@ -1,16 +1,16 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #e8ffd9;
height: 100%;
width: 100%;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #e8ffd9;
height: 100%;
width: 100%;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
+112 -113
View File
@@ -1,4 +1,5 @@
/* istanbul ignore file */
/* eslint-disable */
// This optional code is used to register a service worker.
// register() is not called by default.
@@ -12,135 +13,133 @@
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
),
)
type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void;
onUpdate?: (registration: ServiceWorkerRegistration) => void;
};
onSuccess?: (registration: ServiceWorkerRegistration) => void
onUpdate?: (registration: ServiceWorkerRegistration) => void
}
export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(
process.env.PUBLIC_URL,
window.location.href
);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config)
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA',
)
})
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config)
}
})
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing
if (installingWorker == null) {
return
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.',
)
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration)
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.')
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration)
}
}
}
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
})
.catch((error) => {
console.error('Error during service worker registration:', error)
})
}
function checkValidServiceWorker(swUrl: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' }
})
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
.then((response) => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type')
if (
response.status === 404 ||
(contentType != null &&
contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => {
window.location.reload()
})
})
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config)
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.',
)
})
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then((registration) => {
registration.unregister()
})
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';
import '@testing-library/jest-dom/extend-expect'
+13
View File
@@ -0,0 +1,13 @@
/* istanbul ignore file */
export enum SEX {
male = 'male',
female = 'female',
}
export enum YN {
YES = 1,
NO = 0,
}
export default {}
@@ -0,0 +1,48 @@
import { Action, action } from 'easy-peasy'
import { SEX, YN } from '../enums'
interface PatientInfoFormFields {
firstName?: string
lastName?: string
nickname?: string
sex?: SEX
birthDate?: Date
age?: number
phone?: string
town?: string
firstVisit?: YN
pregnant?: YN
G?: number
P?: number
A?: number
LMP?: Date
wantsPlanning?: YN
weight?: number
height?: number
bloodPressure?: string
temperature?: number
pulse?: number
zScore?: number
albendazole?: YN
allergies?: string
medicines?: string
chiefComplaint?: string[]
history?: string
exam?: string
}
export interface PatientInfoFormModel {
fields: PatientInfoFormFields
updateFields: Action<PatientInfoFormModel, PatientInfoFormFields>
}
/* istanbul ignore next */
// Form submission requests validated in integration test
const patientInfoFormModel: PatientInfoFormModel = {
fields: {},
updateFields: action((state, formObject) => {
state.fields = formObject
}),
}
export default patientInfoFormModel
+8
View File
@@ -0,0 +1,8 @@
/* istanbul ignore file */
import { createStore, persist } from 'easy-peasy'
import storeIndex from './storeIndex'
const store = createStore(persist(storeIndex))
export default store
+17
View File
@@ -0,0 +1,17 @@
/* istanbul ignore file */
import { createTypedHooks } from 'easy-peasy'
import patientInfoFormModel from './models/patientInfoForm.model'
export const storeModels = {
patientInfoFormModel,
}
export const {
useStore,
useStoreState,
useStoreActions,
useStoreDispatch,
} = createTypedHooks<typeof storeModels>()
export default storeModels