diff --git a/.eslintignore b/.eslintignore
index 3b0cce6..78aecbf 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,2 +1,3 @@
webpack.*.js
jest.config.js
+server.js
diff --git a/.eslintrc b/.eslintrc
index 0fc6dbf..3358b89 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -17,5 +17,8 @@
"plugin:@typescript-eslint/recommended", // Uses the recommended rules from @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.
- ]
+ ],
+ "rules": {
+ "react/prop-types": "off"
+ }
}
diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml
new file mode 100644
index 0000000..218bff8
--- /dev/null
+++ b/.github/workflows/node.js.yml
@@ -0,0 +1,30 @@
+# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
+
+name: Node.js CI
+
+on:
+ push:
+ branches: [ development ]
+ pull_request:
+ branches: [ development ]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [12.x, 14.x, 15.x]
+ # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v2
+ with:
+ node-version: ${{ matrix.node-version }}
+ - run: npm ci
+ - run: npm run build --if-present
+ - run: npm test
diff --git a/.gitignore b/.gitignore
index 3c3629e..44d646d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
node_modules
+dist/
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..d1360a6
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,18 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "Jest Current File",
+ "program": "${workspaceFolder}/node_modules/.bin/jest",
+ "args": ["--runTestsByPath", "${relativeFile}"],
+ "console": "integratedTerminal",
+ "internalConsoleOptions": "neverOpen",
+ "disableOptimisticBPs": true,
+ "windows": {
+ "program": "${workspaceFolder}/node_modules/jest/bin/jest"
+ }
+ }
+ ]
+}
diff --git a/Procfile b/Procfile
new file mode 100644
index 0000000..6401e49
--- /dev/null
+++ b/Procfile
@@ -0,0 +1 @@
+web: npm run serve
\ No newline at end of file
diff --git a/README.md b/README.md
index 197f9e4..65f3e6d 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,33 @@
-# user-management
\ No newline at end of file
+# user-management-app
+
+## Overview
+
+This is a demo app designed to demonstrate my React experience. The project UI consists of a single page which diplays a list of users from an Admin perspective.
+
+## Setup
+
+Install all packages
+
+```sh
+npm i
+```
+
+## Available Scripts
+
+In the project directory, you can run:
+
+### `npm run start`
+
+Runs the app in the development mode.\
+Open [http://localhost:8082](http://localhost:8082) to view it in the browser.
+
+The page will reload if you make edits.\
+You will also see any lint errors in the console.
+
+### `npm run test`
+
+Launches the test runner.
+
+### `npm run build`
+
+Generates a new build optimised for production. Files are output to the dist folder.
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 0000000..82ada15
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,12 @@
+module.exports = {
+ preset: "ts-jest",
+ setupFilesAfterEnv: ["./test/setupTests.ts"],
+ moduleNameMapper: {
+ "\\.(css|less|sass|scss)$": "/test/__mocks__/styleMock.js",
+ "\\.(png|gif|ttf|eot)$": "/test/__mocks__/fileMock.js",
+ "\\.(svg)$": "/test/__mocks__/svgIconMock.js",
+ },
+ moduleDirectories: ["src", "node_modules"],
+ collectCoverageFrom: ["src/**/*.tsx", "src/**/*.ts"],
+ testEnvironment: "jsdom",
+};
diff --git a/package-lock.json b/package-lock.json
index 61b1026..a56f0e7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -469,6 +469,15 @@
}
}
},
+ "@fullhuman/postcss-purgecss": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-3.1.3.tgz",
+ "integrity": "sha512-kwOXw8fZ0Lt1QmeOOrd+o4Ibvp4UTEBFQbzvWldjlKv5n+G9sXfIPn1hh63IQIL8K8vbvv1oYMJiIUbuy9bGaA==",
+ "dev": true,
+ "requires": {
+ "purgecss": "^3.1.3"
+ }
+ },
"@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -720,6 +729,12 @@
"fastq": "^1.6.0"
}
},
+ "@polka/url": {
+ "version": "1.0.0-next.12",
+ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.12.tgz",
+ "integrity": "sha512-6RglhutqrGFMO1MNUXp95RBuYIuc8wTnMAV5MUhLmjTOy78ncwOw7RgeQ/HeymkKXRhZd0s2DNrM1rL7unk3MQ==",
+ "dev": true
+ },
"@redux-saga/core": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.1.3.tgz",
@@ -767,6 +782,17 @@
"resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.1.0.tgz",
"integrity": "sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg=="
},
+ "@reduxjs/toolkit": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.5.0.tgz",
+ "integrity": "sha512-E/FUraRx+8guw9Hlg/Ja8jI/hwCrmIKed8Annt9YsZw3BQp+F24t5I5b2OWR6pkEHY4hn1BgP08FrTZFRKsdaQ==",
+ "requires": {
+ "immer": "^8.0.0",
+ "redux": "^4.0.0",
+ "redux-thunk": "^2.3.0",
+ "reselect": "^4.0.0"
+ }
+ },
"@sinonjs/commons": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz",
@@ -1114,6 +1140,15 @@
"redux": "^4.0.0"
}
},
+ "@types/redux-mock-store": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@types/redux-mock-store/-/redux-mock-store-1.0.2.tgz",
+ "integrity": "sha512-6LBtAQBN34i7SI5X+Qs4zpTEZO1tTDZ6sZ9fzFjYwTl3nLQXaBtwYdoV44CzNnyKu438xJ1lSIYyw0YMvunESw==",
+ "dev": true,
+ "requires": {
+ "redux": "^4.0.5"
+ }
+ },
"@types/stack-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz",
@@ -1416,7 +1451,6 @@
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
- "dev": true,
"requires": {
"mime-types": "~2.1.24",
"negotiator": "0.6.2"
@@ -1444,12 +1478,33 @@
"integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
"dev": true
},
+ "acorn-node": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz",
+ "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==",
+ "dev": true,
+ "requires": {
+ "acorn": "^7.0.0",
+ "acorn-walk": "^7.0.0",
+ "xtend": "^4.0.2"
+ }
+ },
"acorn-walk": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
"integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
"dev": true
},
+ "adjust-sourcemap-loader": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-3.0.0.tgz",
+ "integrity": "sha512-YBrGyT2/uVQ/c6Rr+t6ZJXniY03YtHGMJQYal368burRGYKqhx9qGTWqcBU5s1CwYY9E/ri63RYyG1IacMZtqw==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^2.0.0",
+ "regex-parser": "^2.2.11"
+ }
+ },
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -1547,6 +1602,12 @@
"@babel/runtime-corejs3": "^7.10.2"
}
},
+ "arity-n": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz",
+ "integrity": "sha1-2edrEXM+CFacCEeuezmyhgswt0U=",
+ "dev": true
+ },
"arr-diff": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
@@ -1565,6 +1626,12 @@
"integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
"dev": true
},
+ "array-differ": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz",
+ "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==",
+ "dev": true
+ },
"array-flatten": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz",
@@ -1625,6 +1692,12 @@
"function-bind": "^1.1.1"
}
},
+ "arrify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
+ "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
+ "dev": true
+ },
"asn1": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
@@ -1685,6 +1758,12 @@
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
"dev": true
},
+ "at-least-node": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
+ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
+ "dev": true
+ },
"atob": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
@@ -1723,6 +1802,14 @@
"integrity": "sha512-V+Nq70NxKhYt89ArVcaNL9FDryB3vQOd+BFXZIfO3RP6rwtj+2yqqqdHEkacutglPaZLkJeuXKCjCJDMGPtPqg==",
"dev": true
},
+ "axios": {
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
+ "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
+ "requires": {
+ "follow-redirects": "^1.10.0"
+ }
+ },
"axobject-query": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
@@ -1914,7 +2001,6 @@
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
- "dev": true,
"requires": {
"bytes": "3.1.0",
"content-type": "~1.0.4",
@@ -1931,14 +2017,12 @@
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
- "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
- "dev": true
+ "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
"requires": {
"ms": "2.0.0"
}
@@ -1946,14 +2030,12 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
- "dev": true
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
- "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
- "dev": true
+ "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
}
}
},
@@ -2108,6 +2190,12 @@
"integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==",
"dev": true
},
+ "camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true
+ },
"caniuse-lite": {
"version": "1.0.30001192",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001192.tgz",
@@ -2247,9 +2335,9 @@
}
},
"classnames": {
- "version": "2.2.6",
- "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
- "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
+ "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
},
"clean-css": {
"version": "4.2.3",
@@ -2304,6 +2392,16 @@
"object-visit": "^1.0.0"
}
},
+ "color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-3.1.3.tgz",
+ "integrity": "sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.1",
+ "color-string": "^1.5.4"
+ }
+ },
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -2319,6 +2417,16 @@
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
+ "color-string": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz",
+ "integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==",
+ "dev": true,
+ "requires": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
+ },
"colorette": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
@@ -2340,12 +2448,27 @@
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
+ "compare-versions": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz",
+ "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==",
+ "dev": true
+ },
"component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
"dev": true
},
+ "compose-function": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/compose-function/-/compose-function-3.0.3.tgz",
+ "integrity": "sha1-ntZ18TzFRQHTCVCkhv9qe6OrGF8=",
+ "dev": true,
+ "requires": {
+ "arity-n": "^1.0.4"
+ }
+ },
"compressible": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
@@ -2415,7 +2538,6 @@
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
- "dev": true,
"requires": {
"safe-buffer": "5.1.2"
}
@@ -2423,8 +2545,7 @@
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
- "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
- "dev": true
+ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
},
"convert-source-map": {
"version": "1.7.0",
@@ -2438,14 +2559,12 @@
"cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
- "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
- "dev": true
+ "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
- "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
- "dev": true
+ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"copy-descriptor": {
"version": "0.1.1",
@@ -2568,6 +2687,12 @@
}
}
},
+ "css-unit-converter": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz",
+ "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==",
+ "dev": true
+ },
"css-what": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz",
@@ -2615,6 +2740,16 @@
"integrity": "sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g==",
"dev": true
},
+ "d": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
+ "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
+ "dev": true,
+ "requires": {
+ "es5-ext": "^0.10.50",
+ "type": "^1.0.1"
+ }
+ },
"damerau-levenshtein": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz",
@@ -2754,6 +2889,12 @@
}
}
},
+ "defined": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
+ "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=",
+ "dev": true
+ },
"del": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz",
@@ -2819,14 +2960,12 @@
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
- "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
- "dev": true
+ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
},
"destroy": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
- "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
- "dev": true
+ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"detect-newline": {
"version": "3.1.0",
@@ -2840,6 +2979,23 @@
"integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==",
"dev": true
},
+ "detective": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz",
+ "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==",
+ "dev": true,
+ "requires": {
+ "acorn-node": "^1.6.1",
+ "defined": "^1.0.0",
+ "minimist": "^1.1.1"
+ }
+ },
+ "didyoumean": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.1.tgz",
+ "integrity": "sha1-6S7f2tplN9SE1zwBcv0eugxJdv8=",
+ "dev": true
+ },
"diff-sequences": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz",
@@ -2998,6 +3154,12 @@
}
}
},
+ "duplexer": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
+ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
+ "dev": true
+ },
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -3011,8 +3173,7 @@
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
- "dev": true
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"electron-to-chromium": {
"version": "1.3.675",
@@ -3041,8 +3202,7 @@
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
- "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
- "dev": true
+ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"end-of-stream": {
"version": "1.4.4",
@@ -3142,6 +3302,38 @@
"is-symbol": "^1.0.2"
}
},
+ "es5-ext": {
+ "version": "0.10.53",
+ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz",
+ "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==",
+ "dev": true,
+ "requires": {
+ "es6-iterator": "~2.0.3",
+ "es6-symbol": "~3.1.3",
+ "next-tick": "~1.0.0"
+ }
+ },
+ "es6-iterator": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
+ "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
+ "dev": true,
+ "requires": {
+ "d": "1",
+ "es5-ext": "^0.10.35",
+ "es6-symbol": "^3.1.1"
+ }
+ },
+ "es6-symbol": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
+ "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
+ "dev": true,
+ "requires": {
+ "d": "^1.0.1",
+ "ext": "^1.1.2"
+ }
+ },
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -3151,8 +3343,7 @@
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
- "dev": true
+ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
},
"escape-string-regexp": {
"version": "1.0.5",
@@ -3740,8 +3931,7 @@
"etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
- "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
- "dev": true
+ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
"eventemitter3": {
"version": "4.0.7",
@@ -3936,7 +4126,6 @@
"version": "4.17.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
- "dev": true,
"requires": {
"accepts": "~1.3.7",
"array-flatten": "1.1.1",
@@ -3973,14 +4162,12 @@
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
- "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
- "dev": true
+ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
"requires": {
"ms": "2.0.0"
}
@@ -3988,13 +4175,28 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
- "dev": true
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
- "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
+ "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
+ }
+ }
+ },
+ "ext": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz",
+ "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==",
+ "dev": true,
+ "requires": {
+ "type": "^2.0.0"
+ },
+ "dependencies": {
+ "type": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz",
+ "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==",
"dev": true
}
}
@@ -4177,6 +4379,16 @@
"flat-cache": "^3.0.4"
}
},
+ "file-loader": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz",
+ "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0"
+ }
+ },
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -4190,7 +4402,6 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
- "dev": true,
"requires": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
@@ -4205,7 +4416,6 @@
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
"requires": {
"ms": "2.0.0"
}
@@ -4213,8 +4423,7 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
- "dev": true
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
}
},
@@ -4228,6 +4437,15 @@
"path-exists": "^4.0.0"
}
},
+ "find-versions": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz",
+ "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==",
+ "dev": true,
+ "requires": {
+ "semver-regex": "^3.1.2"
+ }
+ },
"flat-cache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
@@ -4247,8 +4465,7 @@
"follow-redirects": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz",
- "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==",
- "dev": true
+ "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA=="
},
"for-in": {
"version": "1.0.2",
@@ -4276,8 +4493,7 @@
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
- "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=",
- "dev": true
+ "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
},
"fraction.js": {
"version": "4.0.13",
@@ -4297,8 +4513,19 @@
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
- "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
- "dev": true
+ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
+ },
+ "fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+ "dev": true,
+ "requires": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ }
},
"fs.realpath": {
"version": "1.0.0",
@@ -4449,6 +4676,15 @@
"dev": true,
"optional": true
},
+ "gzip-size": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz",
+ "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==",
+ "dev": true,
+ "requires": {
+ "duplexer": "^0.1.2"
+ }
+ },
"handle-thing": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
@@ -4639,6 +4875,12 @@
}
}
},
+ "html-tags": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz",
+ "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==",
+ "dev": true
+ },
"html-webpack-plugin": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.2.0.tgz",
@@ -4683,7 +4925,6 @@
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
- "dev": true,
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.3",
@@ -4695,8 +4936,7 @@
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
- "dev": true
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
}
}
},
@@ -4852,16 +5092,75 @@
"dev": true
},
"husky": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/husky/-/husky-5.1.1.tgz",
- "integrity": "sha512-80LZ736V0Nr4/st0c2COYaMbEQhHNmAbLMN8J/kLk7/mo0QdUkUGNDjv/7jVkhug377Wh8wfbWyaVXEJ/h2B/Q==",
- "dev": true
+ "version": "4.3.8",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.8.tgz",
+ "integrity": "sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow==",
+ "dev": true,
+ "requires": {
+ "chalk": "^4.0.0",
+ "ci-info": "^2.0.0",
+ "compare-versions": "^3.6.0",
+ "cosmiconfig": "^7.0.0",
+ "find-versions": "^4.0.0",
+ "opencollective-postinstall": "^2.0.2",
+ "pkg-dir": "^5.0.0",
+ "please-upgrade-node": "^3.2.0",
+ "slash": "^3.0.0",
+ "which-pm-runs": "^1.0.0"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^5.0.0"
+ }
+ },
+ "p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "requires": {
+ "yocto-queue": "^0.1.0"
+ }
+ },
+ "p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^3.0.2"
+ }
+ },
+ "pkg-dir": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz",
+ "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==",
+ "dev": true,
+ "requires": {
+ "find-up": "^5.0.0"
+ }
+ }
+ }
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
- "dev": true,
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
@@ -4878,6 +5177,11 @@
"integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
"dev": true
},
+ "immer": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz",
+ "integrity": "sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA=="
+ },
"import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -4974,8 +5278,7 @@
"ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
- "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
- "dev": true
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
},
"is-absolute-url": {
"version": "3.0.3",
@@ -5944,6 +6247,16 @@
"minimist": "^1.2.5"
}
},
+ "jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.6",
+ "universalify": "^2.0.0"
+ }
+ },
"jsprim": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
@@ -6094,12 +6407,24 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
+ "lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=",
+ "dev": true
+ },
"lodash.sortby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
"integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=",
"dev": true
},
+ "lodash.toarray": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz",
+ "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=",
+ "dev": true
+ },
"loglevel": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz",
@@ -6196,8 +6521,7 @@
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
- "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
- "dev": true
+ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"memory-fs": {
"version": "0.5.0",
@@ -6212,8 +6536,7 @@
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
- "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=",
- "dev": true
+ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
},
"merge-stream": {
"version": "2.0.0",
@@ -6230,8 +6553,7 @@
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
- "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
- "dev": true
+ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
"micromatch": {
"version": "4.0.2",
@@ -6246,20 +6568,17 @@
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
- "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
- "dev": true
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
},
"mime-db": {
"version": "1.46.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz",
- "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==",
- "dev": true
+ "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ=="
},
"mime-types": {
"version": "2.1.29",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz",
"integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==",
- "dev": true,
"requires": {
"mime-db": "1.46.0"
}
@@ -6276,6 +6595,29 @@
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
"dev": true
},
+ "mini-css-extract-plugin": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.5.0.tgz",
+ "integrity": "sha512-SIbuLMv6jsk1FnLIU5OUG/+VMGUprEjM1+o2trOAx8i5KOKMrhyezb1dJ4Ugsykb8Jgq8/w5NEopy6escV9G7g==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0",
+ "webpack-sources": "^1.1.0"
+ },
+ "dependencies": {
+ "webpack-sources": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
+ "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
+ "dev": true,
+ "requires": {
+ "source-list-map": "^2.0.0",
+ "source-map": "~0.6.1"
+ }
+ }
+ }
+ },
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -6324,6 +6666,18 @@
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true
},
+ "modern-normalize": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/modern-normalize/-/modern-normalize-1.0.0.tgz",
+ "integrity": "sha512-1lM+BMLGuDfsdwf3rsgBSrxJwAZHFIrQ8YR61xIqdHo0uNKI9M52wNpHSrliZATJp51On6JD0AfRxd4YGSU0lw==",
+ "dev": true
+ },
+ "mri": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.6.tgz",
+ "integrity": "sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==",
+ "dev": true
+ },
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -6346,6 +6700,19 @@
"integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
"dev": true
},
+ "multimatch": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-4.0.0.tgz",
+ "integrity": "sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==",
+ "dev": true,
+ "requires": {
+ "@types/minimatch": "^3.0.3",
+ "array-differ": "^3.0.0",
+ "array-union": "^2.1.0",
+ "arrify": "^2.0.1",
+ "minimatch": "^3.0.4"
+ }
+ },
"nanoid": {
"version": "3.1.20",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz",
@@ -6380,8 +6747,7 @@
"negotiator": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
- "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
- "dev": true
+ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
"neo-async": {
"version": "2.6.2",
@@ -6389,6 +6755,12 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
+ "next-tick": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
+ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
+ "dev": true
+ },
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@@ -6413,7 +6785,16 @@
}
}
},
- "node-forge": {
+ "node-emoji": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz",
+ "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==",
+ "dev": true,
+ "requires": {
+ "lodash.toarray": "^4.4.0"
+ }
+ },
+ "node-forge": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==",
@@ -6558,6 +6939,12 @@
}
}
},
+ "object-hash": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.1.1.tgz",
+ "integrity": "sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ==",
+ "dev": true
+ },
"object-inspect": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz",
@@ -6656,7 +7043,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
- "dev": true,
"requires": {
"ee-first": "1.1.1"
}
@@ -6685,6 +7071,18 @@
"mimic-fn": "^2.1.0"
}
},
+ "opencollective-postinstall": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
+ "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==",
+ "dev": true
+ },
+ "opener": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
+ "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
+ "dev": true
+ },
"opn": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz",
@@ -6824,8 +7222,7 @@
"parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
- "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
- "dev": true
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
},
"pascal-case": {
"version": "3.1.2",
@@ -6890,8 +7287,7 @@
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
- "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
- "dev": true
+ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"path-type": {
"version": "4.0.0",
@@ -6950,6 +7346,15 @@
"find-up": "^4.0.0"
}
},
+ "please-upgrade-node": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz",
+ "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==",
+ "dev": true,
+ "requires": {
+ "semver-compare": "^1.0.0"
+ }
+ },
"portfinder": {
"version": "1.0.28",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
@@ -6998,6 +7403,58 @@
"source-map": "^0.6.1"
}
},
+ "postcss-functions": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-functions/-/postcss-functions-3.0.0.tgz",
+ "integrity": "sha1-DpTQFERwCkgd4g3k1V+yZAVkJQ4=",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.2",
+ "object-assign": "^4.1.1",
+ "postcss": "^6.0.9",
+ "postcss-value-parser": "^3.3.0"
+ },
+ "dependencies": {
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "postcss": {
+ "version": "6.0.23",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz",
+ "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.4.1",
+ "source-map": "^0.6.1",
+ "supports-color": "^5.4.0"
+ }
+ },
+ "postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-js": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-3.0.3.tgz",
+ "integrity": "sha512-gWnoWQXKFw65Hk/mi2+WTQTHdPD5UJdDXZmX073EY/B3BWnYjO4F4t0VneTCnCGQ5E5GsCdMkzPaTXwl3r5dJw==",
+ "dev": true,
+ "requires": {
+ "camelcase-css": "^2.0.1",
+ "postcss": "^8.1.6"
+ }
+ },
"postcss-loader": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-5.0.0.tgz",
@@ -7044,6 +7501,15 @@
"icss-utils": "^5.0.0"
}
},
+ "postcss-nested": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.4.tgz",
+ "integrity": "sha512-/dimXVqdUAVS2ZiIX0uvyk9UCI825y6LW4TnjG51JTKF89CcorHPAjTUGPF70k2wlQYts5OzfnhYMgfGfHCClQ==",
+ "dev": true,
+ "requires": {
+ "postcss-selector-parser": "^6.0.4"
+ }
+ },
"postcss-selector-parser": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz",
@@ -7143,6 +7609,118 @@
}
}
},
+ "pretty-hrtime": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
+ "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=",
+ "dev": true
+ },
+ "pretty-quick": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/pretty-quick/-/pretty-quick-3.1.0.tgz",
+ "integrity": "sha512-DtxIxksaUWCgPFN7E1ZZk4+Aav3CCuRdhrDSFZENb404sYMtuo9Zka823F+Mgeyt8Zt3bUiCjFzzWYE9LYqkmQ==",
+ "dev": true,
+ "requires": {
+ "chalk": "^3.0.0",
+ "execa": "^4.0.0",
+ "find-up": "^4.1.0",
+ "ignore": "^5.1.4",
+ "mri": "^1.1.5",
+ "multimatch": "^4.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "execa": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz",
+ "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^7.0.0",
+ "get-stream": "^5.0.0",
+ "human-signals": "^1.1.1",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.0",
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2",
+ "strip-final-newline": "^2.0.0"
+ }
+ },
+ "get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "dev": true,
+ "requires": {
+ "pump": "^3.0.0"
+ }
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "is-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
+ "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==",
+ "dev": true
+ },
+ "npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "requires": {
+ "path-key": "^3.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -7179,7 +7757,6 @@
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
"integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
- "dev": true,
"requires": {
"forwarded": "~0.1.2",
"ipaddr.js": "1.9.1"
@@ -7213,6 +7790,26 @@
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"dev": true
},
+ "purgecss": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-3.1.3.tgz",
+ "integrity": "sha512-hRSLN9mguJ2lzlIQtW4qmPS2kh6oMnA9RxdIYK8sz18QYqd6ePp4GNDl18oWHA1f2v2NEQIh51CO8s/E3YGckQ==",
+ "dev": true,
+ "requires": {
+ "commander": "^6.0.0",
+ "glob": "^7.0.0",
+ "postcss": "^8.2.1",
+ "postcss-selector-parser": "^6.0.2"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
+ "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
+ "dev": true
+ }
+ }
+ },
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
@@ -7249,14 +7846,12 @@
"range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
- "dev": true
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
"raw-body": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
- "dev": true,
"requires": {
"bytes": "3.1.0",
"http-errors": "1.7.2",
@@ -7267,8 +7862,7 @@
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
- "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
- "dev": true
+ "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
}
}
},
@@ -7382,6 +7976,24 @@
"strip-indent": "^3.0.0"
}
},
+ "reduce-css-calc": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz",
+ "integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==",
+ "dev": true,
+ "requires": {
+ "css-unit-converter": "^1.1.1",
+ "postcss-value-parser": "^3.3.0"
+ },
+ "dependencies": {
+ "postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "dev": true
+ }
+ }
+ },
"redux": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz",
@@ -7391,6 +8003,15 @@
"symbol-observable": "^1.2.0"
}
},
+ "redux-mock-store": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/redux-mock-store/-/redux-mock-store-1.5.4.tgz",
+ "integrity": "sha512-xmcA0O/tjCLXhh9Fuiq6pMrJCwFRaouA8436zcikdIpYWWCjU76CRk+i2bHx8EeiSiMGnB85/lZdU3wIJVXHTA==",
+ "dev": true,
+ "requires": {
+ "lodash.isplainobject": "^4.0.6"
+ }
+ },
"redux-saga": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.1.3.tgz",
@@ -7399,6 +8020,11 @@
"@redux-saga/core": "^1.1.3"
}
},
+ "redux-thunk": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
+ "integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw=="
+ },
"regenerator-runtime": {
"version": "0.13.7",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
@@ -7414,6 +8040,12 @@
"safe-regex": "^1.1.0"
}
},
+ "regex-parser": {
+ "version": "2.2.11",
+ "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz",
+ "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==",
+ "dev": true
+ },
"regexp.prototype.flags": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz",
@@ -7666,6 +8298,11 @@
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true
},
+ "reselect": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz",
+ "integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA=="
+ },
"resolve": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
@@ -7705,6 +8342,100 @@
"integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
"dev": true
},
+ "resolve-url-loader": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-3.1.2.tgz",
+ "integrity": "sha512-QEb4A76c8Mi7I3xNKXlRKQSlLBwjUV/ULFMP+G7n3/7tJZ8MG5wsZ3ucxP1Jz8Vevn6fnJsxDx9cIls+utGzPQ==",
+ "dev": true,
+ "requires": {
+ "adjust-sourcemap-loader": "3.0.0",
+ "camelcase": "5.3.1",
+ "compose-function": "3.0.3",
+ "convert-source-map": "1.7.0",
+ "es6-iterator": "2.0.3",
+ "loader-utils": "1.2.3",
+ "postcss": "7.0.21",
+ "rework": "1.0.1",
+ "rework-visit": "1.0.0",
+ "source-map": "0.6.1"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "dependencies": {
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "emojis-list": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
+ "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
+ "dev": true
+ },
+ "json5": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+ "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.0"
+ }
+ },
+ "loader-utils": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
+ "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
+ "dev": true,
+ "requires": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^2.0.0",
+ "json5": "^1.0.1"
+ }
+ },
+ "postcss": {
+ "version": "7.0.21",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.21.tgz",
+ "integrity": "sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.4.2",
+ "source-map": "^0.6.1",
+ "supports-color": "^6.1.0"
+ }
+ },
+ "supports-color": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+ "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
"ret": {
"version": "0.1.15",
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
@@ -7723,6 +8454,55 @@
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
"dev": true
},
+ "rework": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/rework/-/rework-1.0.1.tgz",
+ "integrity": "sha1-MIBqhBNCtUUQqkEQhQzUhTQUSqc=",
+ "dev": true,
+ "requires": {
+ "convert-source-map": "^0.3.3",
+ "css": "^2.0.0"
+ },
+ "dependencies": {
+ "convert-source-map": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz",
+ "integrity": "sha1-8dgClQr33SYxof6+BZZVDIarMZA=",
+ "dev": true
+ },
+ "css": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz",
+ "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "source-map": "^0.6.1",
+ "source-map-resolve": "^0.5.2",
+ "urix": "^0.1.0"
+ }
+ },
+ "source-map-resolve": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
+ "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
+ "dev": true,
+ "requires": {
+ "atob": "^2.1.2",
+ "decode-uri-component": "^0.2.0",
+ "resolve-url": "^0.2.1",
+ "source-map-url": "^0.4.0",
+ "urix": "^0.1.0"
+ }
+ }
+ }
+ },
+ "rework-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/rework-visit/-/rework-visit-1.0.0.tgz",
+ "integrity": "sha1-mUWygD8hni96ygCtuLyfZA+ELJo=",
+ "dev": true
+ },
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -7750,8 +8530,7 @@
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"safe-regex": {
"version": "1.1.0",
@@ -7765,8 +8544,7 @@
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "dev": true
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sane": {
"version": "4.1.0",
@@ -7981,11 +8759,22 @@
"lru-cache": "^6.0.0"
}
},
+ "semver-compare": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
+ "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=",
+ "dev": true
+ },
+ "semver-regex": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.2.tgz",
+ "integrity": "sha512-bXWyL6EAKOJa81XG1OZ/Yyuq+oT0b2YLlxx7c+mrdYPaPbnj6WgVULXhinMIeZGufuUBu/eVRqXEhiv4imfwxA==",
+ "dev": true
+ },
"send": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
- "dev": true,
"requires": {
"debug": "2.6.9",
"depd": "~1.1.2",
@@ -8006,7 +8795,6 @@
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
"requires": {
"ms": "2.0.0"
},
@@ -8014,16 +8802,14 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
- "dev": true
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
}
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
- "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
- "dev": true
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
}
}
},
@@ -8096,7 +8882,6 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
- "dev": true,
"requires": {
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
@@ -8136,8 +8921,7 @@
"setprototypeof": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
- "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==",
- "dev": true
+ "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
},
"shallow-clone": {
"version": "3.0.1",
@@ -8187,6 +8971,42 @@
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
"dev": true
},
+ "simple-swizzle": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+ "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=",
+ "dev": true,
+ "requires": {
+ "is-arrayish": "^0.3.1"
+ },
+ "dependencies": {
+ "is-arrayish": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
+ "dev": true
+ }
+ }
+ },
+ "sirv": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.11.tgz",
+ "integrity": "sha512-SR36i3/LSWja7AJNRBz4fF/Xjpn7lQFI30tZ434dIy+bitLYSP+ZEenHg36i23V2SGEz+kqjksg0uOGZ5LPiqg==",
+ "dev": true,
+ "requires": {
+ "@polka/url": "^1.0.0-next.9",
+ "mime": "^2.3.1",
+ "totalist": "^1.0.0"
+ },
+ "dependencies": {
+ "mime": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz",
+ "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==",
+ "dev": true
+ }
+ }
+ },
"sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@@ -8604,8 +9424,7 @@
"statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
- "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
- "dev": true
+ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
},
"stealthy-require": {
"version": "1.1.1",
@@ -8809,6 +9628,34 @@
}
}
},
+ "tailwindcss": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-2.0.3.tgz",
+ "integrity": "sha512-s8NEqdLBiVbbdL0a5XwTb8jKmIonOuI4RMENEcKLR61jw6SdKvBss7NWZzwCaD+ZIjlgmesv8tmrjXEp7C0eAQ==",
+ "dev": true,
+ "requires": {
+ "@fullhuman/postcss-purgecss": "^3.1.3",
+ "bytes": "^3.0.0",
+ "chalk": "^4.1.0",
+ "color": "^3.1.3",
+ "detective": "^5.2.0",
+ "didyoumean": "^1.2.1",
+ "fs-extra": "^9.1.0",
+ "html-tags": "^3.1.0",
+ "lodash": "^4.17.20",
+ "modern-normalize": "^1.0.0",
+ "node-emoji": "^1.8.1",
+ "object-hash": "^2.1.1",
+ "postcss-functions": "^3",
+ "postcss-js": "^3.0.3",
+ "postcss-nested": "^5.0.1",
+ "postcss-selector-parser": "^6.0.4",
+ "postcss-value-parser": "^4.1.0",
+ "pretty-hrtime": "^1.0.3",
+ "reduce-css-calc": "^2.1.8",
+ "resolve": "^1.19.0"
+ }
+ },
"tapable": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
@@ -8954,7 +9801,12 @@
"toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
- "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
+ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
+ },
+ "totalist": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz",
+ "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==",
"dev": true
},
"tough-cookie": {
@@ -9076,6 +9928,12 @@
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"dev": true
},
+ "type": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
+ "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
+ "dev": true
+ },
"type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -9101,7 +9959,6 @@
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
- "dev": true,
"requires": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
@@ -9161,11 +10018,16 @@
"integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
"dev": true
},
+ "universalify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
+ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
+ "dev": true
+ },
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
- "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
- "dev": true
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
},
"unset-value": {
"version": "1.0.0",
@@ -9277,8 +10139,7 @@
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
- "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
- "dev": true
+ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
"uuid": {
"version": "8.3.2",
@@ -9325,8 +10186,7 @@
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
- "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
- "dev": true
+ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
},
"verror": {
"version": "1.10.0",
@@ -9446,6 +10306,43 @@
}
}
},
+ "webpack-bundle-analyzer": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.1.tgz",
+ "integrity": "sha512-j5m7WgytCkiVBoOGavzNokBOqxe6Mma13X1asfVYtKWM3wxBiRRu1u1iG0Iol5+qp9WgyhkMmBAcvjEfJ2bdDw==",
+ "dev": true,
+ "requires": {
+ "acorn": "^8.0.4",
+ "acorn-walk": "^8.0.0",
+ "chalk": "^4.1.0",
+ "commander": "^6.2.0",
+ "gzip-size": "^6.0.0",
+ "lodash": "^4.17.20",
+ "opener": "^1.5.2",
+ "sirv": "^1.0.7",
+ "ws": "^7.3.1"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.2.1.tgz",
+ "integrity": "sha512-z716cpm5TX4uzOzILx8PavOE6C6DKshHDw1aQN52M/yNSqE9s5O8SMfyhCCfCJ3HmTL0NkVOi+8a/55T7YB3bg==",
+ "dev": true
+ },
+ "acorn-walk": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.1.0.tgz",
+ "integrity": "sha512-mjmzmv12YIG/G8JQdQuz2MUDShEJ6teYpT5bmWA4q7iwoGen8xtt3twF3OvzIUl+Q06aWIjvnwQUKvQ6TtMRjg==",
+ "dev": true
+ },
+ "commander": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
+ "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
+ "dev": true
+ }
+ }
+ },
"webpack-cli": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.5.0.tgz",
@@ -10151,6 +11048,12 @@
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
+ "which-pm-runs": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz",
+ "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=",
+ "dev": true
+ },
"wildcard": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz",
@@ -10236,6 +11139,12 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true
},
+ "xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "dev": true
+ },
"y18n": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
diff --git a/package.json b/package.json
index 02e88d6..a1c1d68 100644
--- a/package.json
+++ b/package.json
@@ -4,15 +4,28 @@
"description": "",
"main": "src/index.tsx",
"scripts": {
- "start": "webpack serve",
- "test": "echo \"Error: no test specified\" && exit 1",
+ "build": "rimraf dist && NODE_ENV=production webpack --config webpack.prod.js",
+ "start": "webpack serve --config webpack.dev.js",
+ "serve": "node src/server.js",
+ "test": "jest --env=jsdom",
+ "test:coverage": "jest --coverage",
"lint": "eslint . --ext .ts,.tsx",
- "lint:fix": "eslint . --fix"
+ "lint:fix": "eslint . --fix",
+ "pretty": "pretty-quick --branch development",
+ "prettier-format": "prettier --config .prettierrc '**/*.{json,js,jsx,ts,tsx,css,scss,md}' --write",
+ "postinstall": "npm run build"
},
"author": "Sean Jones",
"license": "ISC",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/cssmonkey/user-management.git"
+ },
"dependencies": {
- "classnames": "^2.2.6",
+ "@reduxjs/toolkit": "^1.5.0",
+ "axios": "^0.21.1",
+ "classnames": "^2.3.1",
+ "express": "^4.17.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-redux": "^7.2.2",
@@ -29,6 +42,7 @@
"@types/node": "^14.14.31",
"@types/react-dom": "^17.0.1",
"@types/react-redux": "^7.1.16",
+ "@types/redux-mock-store": "^1.0.2",
"@typescript-eslint/eslint-plugin": "^4.15.2",
"@typescript-eslint/parser": "^4.15.2",
"autoprefixer": "^10.2.4",
@@ -43,26 +57,39 @@
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.2.0",
+ "file-loader": "^6.2.0",
"html-webpack-plugin": "^5.2.0",
- "husky": "^5.1.1",
+ "husky": "^4.3.8",
"jest": "^26.6.3",
+ "mini-css-extract-plugin": "^1.5.0",
"postcss": "^8.2.6",
"postcss-loader": "^5.0.0",
"prettier": "^2.2.1",
"prettier-quick": "0.0.5",
+ "pretty-quick": "^3.1.0",
+ "redux-mock-store": "^1.5.4",
+ "resolve-url-loader": "^3.1.2",
+ "rimraf": "^3.0.2",
"sass": "^1.32.8",
"sass-loader": "^11.0.1",
"style-loader": "^2.0.0",
+ "tailwindcss": "^2.0.3",
"ts-jest": "^26.5.2",
"ts-loader": "^8.0.17",
"typescript": "^4.2.2",
"webpack": "^5.24.2",
+ "webpack-bundle-analyzer": "^4.4.1",
"webpack-cli": "^4.5.0",
- "webpack-dev-server": "^3.11.2"
+ "webpack-dev-server": "^3.11.2",
+ "webpack-merge": "^5.7.3"
},
"husky": {
"hooks": {
- "pre-commit": "npm run lint && npm run pretty -- --staged"
+ "pre-commit": "npm run lint && npm run pretty -- --staged",
+ "pre-push": "npm run test -- --bail"
}
+ },
+ "engines": {
+ "node": "12.14.1"
}
}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000..12a703d
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+};
diff --git a/src/api/apiConfig.ts b/src/api/apiConfig.ts
new file mode 100644
index 0000000..624a9cf
--- /dev/null
+++ b/src/api/apiConfig.ts
@@ -0,0 +1,8 @@
+const API_BASE_URL = "https://jsonplaceholder.typicode.com";
+
+export default {
+ endpoints: {
+ userProfile: (userId: string): string => `${API_BASE_URL}/users/${userId}`,
+ users: `${API_BASE_URL}/users`,
+ },
+};
diff --git a/src/api/apiService.ts b/src/api/apiService.ts
new file mode 100644
index 0000000..19fef0f
--- /dev/null
+++ b/src/api/apiService.ts
@@ -0,0 +1,8 @@
+import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
+class ApiService {
+ async callApi(config: AxiosRequestConfig): Promise> {
+ return axios.request(config);
+ }
+}
+
+export default new ApiService();
diff --git a/src/assets/images/logo.svg b/src/assets/images/logo.svg
new file mode 100644
index 0000000..10cf6ca
--- /dev/null
+++ b/src/assets/images/logo.svg
@@ -0,0 +1,22 @@
+
+
\ No newline at end of file
diff --git a/src/components/App/App.test.tsx b/src/components/App/App.test.tsx
new file mode 100644
index 0000000..215d71f
--- /dev/null
+++ b/src/components/App/App.test.tsx
@@ -0,0 +1,29 @@
+import React from "react";
+import renderConnected from "utilities/test/renderConnected";
+
+import App from "./App";
+
+const setup = (initialState = {}) =>
+ renderConnected({
+ ui: ,
+ initialState,
+ });
+
+describe("App", () => {
+ it("renders Loader when userProfile not loaded", () => {
+ const { container } = setup({
+ userProfile: {},
+ });
+ const app = container.querySelector(".loader");
+ expect(app).toBeInTheDocument();
+ });
+ it("renders App when userProfile loaded", () => {
+ const { container } = setup({
+ userProfile: {
+ username: "Steve",
+ },
+ });
+ const app = container.querySelector(".app");
+ expect(app).toBeInTheDocument();
+ });
+});
diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx
index 5a78983..1947fb2 100644
--- a/src/components/App/App.tsx
+++ b/src/components/App/App.tsx
@@ -1,5 +1,37 @@
-import React, { FC } from "react";
+import React, { FC, useEffect } from "react";
+import { useDispatch, useSelector } from "react-redux";
-const App: FC = () => App
;
+import { fetchUserById } from "state/userProfile/actions";
+import { getUsername } from "state/userProfile/selectors";
+
+import Loader from "../Loader/Loader";
+import SiteHeader from "../SiteHeader/SiteHeader";
+import LandingPage from "pages/LandingPage/LandingPage";
+
+import "styles/app.scss";
+
+const App: FC = () => {
+ const dispatch = useDispatch();
+ const username = useSelector(getUsername);
+
+ useEffect(() => {
+ if (!username) {
+ setTimeout(() => dispatch(fetchUserById("1")), 2400);
+ }
+ }, [dispatch]);
+
+ const isPageReady = username;
+
+ if (!isPageReady) {
+ return ;
+ }
+
+ return (
+
+
+
+
+ );
+};
export default App;
diff --git a/src/components/ContentCard/ContentCard.test.tsx b/src/components/ContentCard/ContentCard.test.tsx
new file mode 100644
index 0000000..ba8a560
--- /dev/null
+++ b/src/components/ContentCard/ContentCard.test.tsx
@@ -0,0 +1,17 @@
+import React from "react";
+import { screen, render } from "@testing-library/react";
+
+import ContentCard from "./ContentCard";
+
+describe("ContentCard", () => {
+ it("renders", () => {
+ render(
+
+ Child element
+
+ );
+
+ const childElement = screen.getByText("Child element");
+ expect(childElement).toBeInTheDocument();
+ });
+});
diff --git a/src/components/ContentCard/ContentCard.tsx b/src/components/ContentCard/ContentCard.tsx
new file mode 100644
index 0000000..a175570
--- /dev/null
+++ b/src/components/ContentCard/ContentCard.tsx
@@ -0,0 +1,11 @@
+import React, { FC } from "react";
+
+interface ContentCardProps {
+ children: JSX.Element[] | JSX.Element;
+}
+
+const ContentCard: FC = ({ children }) => (
+ {children}
+);
+
+export default ContentCard;
diff --git a/src/components/Loader/Loader.test.tsx b/src/components/Loader/Loader.test.tsx
new file mode 100644
index 0000000..e5790af
--- /dev/null
+++ b/src/components/Loader/Loader.test.tsx
@@ -0,0 +1,14 @@
+import React from "react";
+import { screen, render } from "@testing-library/react";
+
+import Loader from "./Loader";
+
+const setup = () => render();
+
+describe("Loader", () => {
+ it("should display loading message", () => {
+ setup();
+ const loadingText = screen.getByText("Loading...");
+ expect(loadingText).toBeInTheDocument();
+ });
+});
diff --git a/src/components/Loader/Loader.tsx b/src/components/Loader/Loader.tsx
new file mode 100644
index 0000000..e25f612
--- /dev/null
+++ b/src/components/Loader/Loader.tsx
@@ -0,0 +1,11 @@
+import React, { FC } from "react";
+
+const Loader: FC = () => (
+
+);
+
+export default Loader;
diff --git a/src/components/PageContainer/PageContainer.test.tsx b/src/components/PageContainer/PageContainer.test.tsx
new file mode 100644
index 0000000..8334241
--- /dev/null
+++ b/src/components/PageContainer/PageContainer.test.tsx
@@ -0,0 +1,17 @@
+import React from "react";
+import { screen, render } from "@testing-library/react";
+
+import PageContainer from "./PageContainer";
+
+describe("PageContainer", () => {
+ it("renders", () => {
+ render(
+
+ Child element
+
+ );
+
+ const childElement = screen.getByText("Child element");
+ expect(childElement).toBeInTheDocument();
+ });
+});
diff --git a/src/components/PageContainer/PageContainer.tsx b/src/components/PageContainer/PageContainer.tsx
new file mode 100644
index 0000000..6817aed
--- /dev/null
+++ b/src/components/PageContainer/PageContainer.tsx
@@ -0,0 +1,13 @@
+import React, { FC } from "react";
+
+interface PageContainerProps {
+ children: JSX.Element[] | JSX.Element;
+}
+
+const PageContainer: FC = ({ children }) => (
+
+);
+
+export default PageContainer;
diff --git a/src/components/SiteHeader/SiteHeader.test.tsx b/src/components/SiteHeader/SiteHeader.test.tsx
new file mode 100644
index 0000000..4776995
--- /dev/null
+++ b/src/components/SiteHeader/SiteHeader.test.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+import { screen } from "@testing-library/react";
+import renderConnected from "utilities/test/renderConnected";
+
+import SiteHeader from "./SiteHeader";
+
+const setup = () =>
+ renderConnected({
+ ui: ,
+ initialState: {
+ userProfile: {
+ username: "Steve",
+ },
+ },
+ });
+
+describe("SiteHeader", () => {
+ it("should display logo", () => {
+ setup();
+ const logoImg = screen.getByAltText("Company name");
+ expect(logoImg).toBeInTheDocument();
+ });
+});
diff --git a/src/components/SiteHeader/SiteHeader.tsx b/src/components/SiteHeader/SiteHeader.tsx
new file mode 100644
index 0000000..392c335
--- /dev/null
+++ b/src/components/SiteHeader/SiteHeader.tsx
@@ -0,0 +1,13 @@
+import React, { FC } from "react";
+
+import Logo from "assets/images/logo.svg";
+import UserAvatar from "../UserAvatar/UserAvatar";
+
+const SiteHeader: FC = () => (
+
+

+
+
+);
+
+export default SiteHeader;
diff --git a/src/components/UserAvatar/UserAvatar.test.tsx b/src/components/UserAvatar/UserAvatar.test.tsx
new file mode 100644
index 0000000..f6908ab
--- /dev/null
+++ b/src/components/UserAvatar/UserAvatar.test.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+import { screen } from "@testing-library/react";
+import renderConnected from "utilities/test/renderConnected";
+
+import UserAvatar from "./UserAvatar";
+
+const setup = (initialState = {}) =>
+ renderConnected({
+ ui: ,
+ initialState,
+ });
+
+describe("UserAvatar", () => {
+ it("should display username", () => {
+ setup({
+ userProfile: {
+ username: "Steve",
+ },
+ });
+ const userAvatarText = screen.getByText("Logged in as Steve");
+ expect(userAvatarText).toBeInTheDocument();
+ });
+});
diff --git a/src/components/UserAvatar/UserAvatar.tsx b/src/components/UserAvatar/UserAvatar.tsx
new file mode 100644
index 0000000..a11617a
--- /dev/null
+++ b/src/components/UserAvatar/UserAvatar.tsx
@@ -0,0 +1,17 @@
+import React, { FC } from "react";
+import { useSelector } from "react-redux";
+import classNames from "classnames";
+
+import { getUsername } from "state/userProfile/selectors";
+
+interface UserHeaderProps {
+ className?: string;
+}
+
+const UserHeader: FC = ({ className }) => {
+ const userName = useSelector(getUsername);
+ const classes = classNames("user-profile", className);
+ return Logged in as {userName}
;
+};
+
+export default UserHeader;
diff --git a/src/components/UsersTable/UsersTable.tsx b/src/components/UsersTable/UsersTable.tsx
new file mode 100644
index 0000000..15ffb8f
--- /dev/null
+++ b/src/components/UsersTable/UsersTable.tsx
@@ -0,0 +1,49 @@
+import React, { FC, useEffect } from "react";
+import { useDispatch, useSelector } from "react-redux";
+
+import { getUsersList } from "state/users/selectors";
+import { fetchUsers } from "state/users/actions";
+
+import "./users-table.scss";
+
+const UsersTable: FC = () => {
+ const dispatch = useDispatch();
+ const usersList = useSelector(getUsersList);
+
+ useEffect(() => {
+ if (!usersList) {
+ dispatch(fetchUsers());
+ }
+ }, [dispatch]);
+
+ if (!usersList || usersList.length === 0) {
+ return null;
+ }
+
+ return (
+
+
+
+
+ | Name |
+ Username |
+ Email |
+ Company |
+
+
+
+ {usersList.map(({ name, email, company, username }, i) => (
+
+ | {name} |
+ {email} |
+ {company.name} |
+ {username} |
+
+ ))}
+
+
+
+ );
+};
+
+export default UsersTable;
diff --git a/src/components/UsersTable/users-table.scss b/src/components/UsersTable/users-table.scss
new file mode 100644
index 0000000..5b2734c
--- /dev/null
+++ b/src/components/UsersTable/users-table.scss
@@ -0,0 +1,19 @@
+.users-table {
+ table {
+ width: 100%;
+ border-collapse: collapse;
+ border-spacing: 0;
+ }
+
+ thead th,
+ thead td {
+ font-size: 1rem;
+ padding: 1rem;
+ text-align: left;
+ }
+
+ tbody td {
+ font-size: 1.4rem;
+ padding: 0.25rem 1rem;
+ }
+}
diff --git a/src/index.tsx b/src/index.tsx
index 316b54d..a961e10 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,6 +1,13 @@
import React from "react";
import { render } from "react-dom";
+import { Provider } from "react-redux";
-import App from "./components/App/App";
+import store from "state/store";
+import App from "components/App/App";
-render(, document.getElementById("app"));
+render(
+
+
+ ,
+ document.getElementById("app")
+);
diff --git a/src/pages/LandingPage/LandingPage.test.tsx b/src/pages/LandingPage/LandingPage.test.tsx
new file mode 100644
index 0000000..b5faa68
--- /dev/null
+++ b/src/pages/LandingPage/LandingPage.test.tsx
@@ -0,0 +1,20 @@
+import React from "react";
+import { screen } from "@testing-library/react";
+import renderConnected from "utilities/test/renderConnected";
+
+import LandingPage from "./LandingPage";
+
+const setup = (initialState = {}) =>
+ renderConnected({
+ ui: ,
+ initialState,
+ });
+
+describe("LandingPage", () => {
+ it("renders", () => {
+ setup();
+
+ const heading = screen.getByText("Landing page");
+ expect(heading).toBeInTheDocument();
+ });
+});
diff --git a/src/pages/LandingPage/LandingPage.tsx b/src/pages/LandingPage/LandingPage.tsx
new file mode 100644
index 0000000..05adcae
--- /dev/null
+++ b/src/pages/LandingPage/LandingPage.tsx
@@ -0,0 +1,19 @@
+import React, { FC } from "react";
+
+import PageContainer from "components/PageContainer/PageContainer";
+import ContentCard from "components/ContentCard/ContentCard";
+import UsersTable from "components/UsersTable/UsersTable";
+
+const LandingPage: FC = () => {
+ return (
+
+ Landing page
+ Lorem ipsum dolor sit amet consectetur adipisicing elit.
+
+
+
+
+ );
+};
+
+export default LandingPage;
diff --git a/src/server.js b/src/server.js
new file mode 100644
index 0000000..46a72f0
--- /dev/null
+++ b/src/server.js
@@ -0,0 +1,6 @@
+const express = require("express");
+const app = express();
+
+app.use(express.static(__dirname + "/dist"));
+
+app.listen(process.env.PORT || 8080);
diff --git a/src/state/rootReducer.ts b/src/state/rootReducer.ts
new file mode 100644
index 0000000..a6f2db4
--- /dev/null
+++ b/src/state/rootReducer.ts
@@ -0,0 +1,11 @@
+import { combineReducers } from "@reduxjs/toolkit";
+
+import userProfile from "./userProfile/reducer";
+import users from "./users/reducer";
+
+export const reducers = { userProfile, users } as const;
+
+const rootReducer = combineReducers(reducers);
+
+export type RootState = ReturnType;
+export default rootReducer;
diff --git a/src/state/store.ts b/src/state/store.ts
new file mode 100644
index 0000000..1ae2c60
--- /dev/null
+++ b/src/state/store.ts
@@ -0,0 +1,10 @@
+import { configureStore } from "@reduxjs/toolkit";
+
+import rootReducer from "./rootReducer";
+
+const store = configureStore({
+ reducer: rootReducer,
+ devTools: process.env.NODE_ENV !== "production",
+});
+
+export default store;
diff --git a/src/state/types.ts b/src/state/types.ts
new file mode 100644
index 0000000..1be73cf
--- /dev/null
+++ b/src/state/types.ts
@@ -0,0 +1,11 @@
+export interface User {
+ id?: string;
+ name?: string;
+ email?: string;
+ username?: string;
+ company?: {
+ name: string;
+ };
+}
+
+export type Users = User[];
diff --git a/src/state/userProfile/actions.ts b/src/state/userProfile/actions.ts
new file mode 100644
index 0000000..9f30dbc
--- /dev/null
+++ b/src/state/userProfile/actions.ts
@@ -0,0 +1,17 @@
+import { createAsyncThunk } from "@reduxjs/toolkit";
+import ApiService from "api/apiService";
+import apiConfig from "api/apiConfig";
+
+import { User } from "../types";
+
+export const ACTION_TYPES = {
+ FETCH_USER_BY_ID: "users/fetchById",
+} as const;
+
+export const fetchUserById = createAsyncThunk("users/fetchById", async (userId: string) => {
+ const response = await ApiService.callApi({
+ url: apiConfig.endpoints.userProfile(userId),
+ method: "GET",
+ });
+ return response.data;
+});
diff --git a/src/state/userProfile/reducer.test.ts b/src/state/userProfile/reducer.test.ts
new file mode 100644
index 0000000..e1ec399
--- /dev/null
+++ b/src/state/userProfile/reducer.test.ts
@@ -0,0 +1,18 @@
+import reducer, { initialState } from "./reducer";
+import { ACTION_TYPES } from "./actions";
+import { User } from "../types";
+
+describe("UserProfile reducer", () => {
+ it("Fetch user by id fulfilled", () => {
+ const user: User = {
+ id: "1",
+ name: "Steve Smith",
+ };
+ const state = reducer(initialState, {
+ type: `${ACTION_TYPES.FETCH_USER_BY_ID}/fulfilled`,
+ payload: user,
+ });
+ expect(state.id).toBe("1");
+ expect(state.name).toBe("Steve Smith");
+ });
+});
diff --git a/src/state/userProfile/reducer.ts b/src/state/userProfile/reducer.ts
new file mode 100644
index 0000000..a086b31
--- /dev/null
+++ b/src/state/userProfile/reducer.ts
@@ -0,0 +1,17 @@
+import { createReducer } from "@reduxjs/toolkit";
+
+import { User } from "../types";
+import { fetchUserById } from "./actions";
+
+export const initialState = {} as User;
+
+const userProfileReducer = createReducer(initialState, (builder) => {
+ builder.addCase(fetchUserById.fulfilled, (state, action) => {
+ return {
+ ...state,
+ ...action.payload,
+ };
+ });
+});
+
+export default userProfileReducer;
diff --git a/src/state/userProfile/selectors.test.ts b/src/state/userProfile/selectors.test.ts
new file mode 100644
index 0000000..99798bf
--- /dev/null
+++ b/src/state/userProfile/selectors.test.ts
@@ -0,0 +1,17 @@
+import { getUsername } from "./selectors";
+import mockState from "utilities/test/mockState";
+import { RootState } from "../rootReducer";
+
+describe("UserProfile selectors", () => {
+ describe("getUsername", () => {
+ it("should return username", () => {
+ const state: RootState = {
+ ...mockState,
+ userProfile: {
+ username: "Steve",
+ },
+ };
+ expect(getUsername(state)).toBe("Steve");
+ });
+ });
+});
diff --git a/src/state/userProfile/selectors.ts b/src/state/userProfile/selectors.ts
new file mode 100644
index 0000000..d12acfc
--- /dev/null
+++ b/src/state/userProfile/selectors.ts
@@ -0,0 +1,3 @@
+import { RootState } from "../rootReducer";
+
+export const getUsername = (state: RootState): string => state.userProfile.username;
diff --git a/src/state/users/actions.ts b/src/state/users/actions.ts
new file mode 100644
index 0000000..e6259e3
--- /dev/null
+++ b/src/state/users/actions.ts
@@ -0,0 +1,17 @@
+import { createAsyncThunk } from "@reduxjs/toolkit";
+import ApiService from "api/apiService";
+import apiConfig from "api/apiConfig";
+
+import { Users } from "../types";
+
+export const ACTION_TYPES = {
+ FETCH_USERS: "users/fetchUsers",
+} as const;
+
+export const fetchUsers = createAsyncThunk(ACTION_TYPES.FETCH_USERS, async () => {
+ const response = await ApiService.callApi({
+ url: apiConfig.endpoints.users,
+ method: "GET",
+ });
+ return response.data;
+});
diff --git a/src/state/users/reducer.test.ts b/src/state/users/reducer.test.ts
new file mode 100644
index 0000000..3c850e7
--- /dev/null
+++ b/src/state/users/reducer.test.ts
@@ -0,0 +1,19 @@
+import reducer, { initialState } from "./reducer";
+import { ACTION_TYPES } from "./actions";
+import { Users } from "../types";
+
+describe("Users reducer", () => {
+ it("Fetch users fulfilled", () => {
+ const usersList: Users = [
+ {
+ id: "1",
+ name: "Steve Smith",
+ },
+ ];
+ const state = reducer(initialState, {
+ type: `${ACTION_TYPES.FETCH_USERS}/fulfilled`,
+ payload: usersList,
+ });
+ expect(state.list).toEqual(usersList);
+ });
+});
diff --git a/src/state/users/reducer.ts b/src/state/users/reducer.ts
new file mode 100644
index 0000000..391625d
--- /dev/null
+++ b/src/state/users/reducer.ts
@@ -0,0 +1,21 @@
+import { createReducer } from "@reduxjs/toolkit";
+
+import { Users } from "../types";
+import { fetchUsers } from "./actions";
+
+interface UsersState {
+ list: Users | null;
+}
+
+export const initialState = {} as UsersState;
+
+const usersReducer = createReducer(initialState, (builder) => {
+ builder.addCase(fetchUsers.fulfilled, (state, action) => {
+ return {
+ ...state,
+ list: action.payload,
+ };
+ });
+});
+
+export default usersReducer;
diff --git a/src/state/users/selectors.test.ts b/src/state/users/selectors.test.ts
new file mode 100644
index 0000000..2aab54d
--- /dev/null
+++ b/src/state/users/selectors.test.ts
@@ -0,0 +1,24 @@
+import { getUsersList } from "./selectors";
+import mockState from "utilities/test/mockState";
+import { RootState } from "../rootReducer";
+import { Users } from "../types";
+
+describe("Users selectors", () => {
+ describe("getUsersList", () => {
+ it("should return list of users", () => {
+ const usersList: Users = [
+ {
+ id: "1",
+ name: "Steve Smith",
+ },
+ ];
+ const state: RootState = {
+ ...mockState,
+ users: {
+ list: usersList,
+ },
+ };
+ expect(getUsersList(state)).toEqual(usersList);
+ });
+ });
+});
diff --git a/src/state/users/selectors.ts b/src/state/users/selectors.ts
new file mode 100644
index 0000000..e2a86be
--- /dev/null
+++ b/src/state/users/selectors.ts
@@ -0,0 +1,4 @@
+import { RootState } from "../rootReducer";
+import { User } from "../types";
+
+export const getUsersList = (state: RootState): User[] => state.users.list;
diff --git a/src/styles/app.scss b/src/styles/app.scss
new file mode 100644
index 0000000..76fcadc
--- /dev/null
+++ b/src/styles/app.scss
@@ -0,0 +1,3 @@
+@import "tailwindcss/base";
+@import "tailwindcss/components";
+@import "tailwindcss/utilities";
diff --git a/src/types/index.d.ts b/src/types/index.d.ts
new file mode 100644
index 0000000..e4d7e6d
--- /dev/null
+++ b/src/types/index.d.ts
@@ -0,0 +1,9 @@
+declare module "*.svg" {
+ const content: string;
+ export default content;
+}
+
+declare module "*.png" {
+ const content: string;
+ export default content;
+}
diff --git a/src/utilities/test/mockState.ts b/src/utilities/test/mockState.ts
new file mode 100644
index 0000000..b129078
--- /dev/null
+++ b/src/utilities/test/mockState.ts
@@ -0,0 +1,9 @@
+import { initialState as userProfileInitialState } from "state/userProfile/reducer";
+import { initialState as usersInitialState } from "state/users/reducer";
+
+const state = {
+ userProfile: userProfileInitialState,
+ users: usersInitialState,
+};
+
+export default state;
diff --git a/src/utilities/test/renderConnected.tsx b/src/utilities/test/renderConnected.tsx
new file mode 100644
index 0000000..90c74f4
--- /dev/null
+++ b/src/utilities/test/renderConnected.tsx
@@ -0,0 +1,37 @@
+import React from "react";
+import { render, RenderResult } from "@testing-library/react";
+import { Provider } from "react-redux";
+import configureStore, { MockStore } from "redux-mock-store";
+import thunk from "redux-thunk";
+
+import { reducers } from "state/rootReducer";
+
+interface RenderConnected {
+ ui: JSX.Element;
+ initialState: Record;
+ store?: MockStore;
+}
+
+interface RenderConnectedWithStore extends RenderResult {
+ store: MockStore;
+}
+
+const middleware = [thunk];
+const mockStore = configureStore(middleware);
+
+export const makeStore = (initialState: Record): MockStore =>
+ mockStore({ ...reducers, ...initialState });
+
+const renderConnected = ({
+ ui,
+ initialState = {},
+ store = makeStore(initialState),
+}: RenderConnected): RenderConnectedWithStore => {
+ const rendered = render({ui});
+ return {
+ ...rendered,
+ store,
+ };
+};
+
+export default renderConnected;
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..8887a4f
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,11 @@
+module.exports = {
+ purge: ["./src/**/*.{tsx,ts}"],
+ darkMode: false, // or 'media' or 'class'
+ theme: {
+ extend: {},
+ },
+ variants: {
+ extend: {},
+ },
+ plugins: [],
+};
diff --git a/test/__mocks__/fileMock.js b/test/__mocks__/fileMock.js
new file mode 100644
index 0000000..e69de29
diff --git a/test/__mocks__/styleMock.js b/test/__mocks__/styleMock.js
new file mode 100644
index 0000000..e69de29
diff --git a/test/__mocks__/svgIconMock.js b/test/__mocks__/svgIconMock.js
new file mode 100644
index 0000000..e69de29
diff --git a/test/setupTests.ts b/test/setupTests.ts
new file mode 100644
index 0000000..1dd407a
--- /dev/null
+++ b/test/setupTests.ts
@@ -0,0 +1,5 @@
+// jest-dom adds custom jest matchers for asserting on DOM nodes.
+// 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";
diff --git a/tsconfig.json b/tsconfig.json
index ec353f3..380d190 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -6,6 +6,15 @@
"outDir": "./dist",
"jsx": "react",
"esModuleInterop": true,
- "allowSyntheticDefaultImports": true
- },
-}
\ No newline at end of file
+ "allowSyntheticDefaultImports": true,
+ "paths": {
+ "api/*": ["./src/api/*"],
+ "assets/*": ["./src/assets/*"],
+ "components/*": ["./src/components/*"],
+ "pages/*": ["./src/pages/*"],
+ "state/*": ["./src/state/*"],
+ "styles/*": ["./src/styles/*"],
+ "utilities/*": ["./src/utilities/*"]
+ }
+ }
+}
diff --git a/webpack.common.js b/webpack.common.js
new file mode 100644
index 0000000..fd21e99
--- /dev/null
+++ b/webpack.common.js
@@ -0,0 +1,74 @@
+const path = require("path");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+
+const sourcePath = path.join(__dirname, "./src");
+const publicPath = path.join(__dirname, "./public");
+
+module.exports = () => ({
+ entry: {
+ app: `${sourcePath}/index.tsx`,
+ },
+ resolve: {
+ modules: ["node_modules"],
+ extensions: [".ts", ".tsx", ".js", ".jsx"],
+ alias: {
+ api: path.resolve(sourcePath, "api/"),
+ assets: path.resolve(sourcePath, "assets/"),
+ components: path.resolve(sourcePath, "components/"),
+ pages: path.resolve(sourcePath, "pages/"),
+ state: path.resolve(sourcePath, "state/"),
+ styles: path.resolve(sourcePath, "styles/"),
+ utilities: path.resolve(sourcePath, "utilities/"),
+ },
+ },
+ module: {
+ rules: [
+ {
+ test: /\.(ts)x?$/,
+ exclude: /node_modules/,
+ use: {
+ loader: "ts-loader",
+ },
+ },
+ {
+ test: /\.(scss|css)$/,
+ use: [
+ {
+ loader: "style-loader",
+ },
+ {
+ loader: "css-loader",
+ },
+ {
+ loader: "postcss-loader",
+ },
+ {
+ loader: "sass-loader",
+ options: {
+ sourceMap: true,
+ },
+ },
+ ],
+ exclude: /node_modules/,
+ },
+ {
+ test: /\.(jpe?g|png|gif|svg)$/i,
+ include: path.resolve(sourcePath, "assets/images"),
+ use: {
+ loader: "file-loader",
+ options: {
+ name: "[name].[ext]",
+ outputPath: "assets/images/",
+ },
+ },
+ },
+ ],
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ chunks: ["vendor", "app"],
+ filename: "index.html",
+ template: path.join(publicPath, "/index.html"),
+ }),
+ ],
+});
diff --git a/webpack.config.js b/webpack.config.js
deleted file mode 100644
index 5ce4a9e..0000000
--- a/webpack.config.js
+++ /dev/null
@@ -1,75 +0,0 @@
-const path = require('path');
-const HtmlWebpackPlugin = require('html-webpack-plugin');
-
-const buildPath = path.join(__dirname, './dist');
-const sourcePath = path.join(__dirname, './src');
-const publicPath = path.join(__dirname, './public');
-
-module.exports = () => ({
- entry: {
- app: `${sourcePath}/index.tsx`,
- },
- output: {
- path: buildPath,
- filename: '[name].[hash].js',
- chunkFilename: '[name].[hash].js',
- },
- mode: 'development',
- devtool: 'source-map',
- devServer: {
- contentBase: './dist',
- historyApiFallback: true,
- compress: true,
- open: true,
- hot: true,
- overlay: true,
- port: 8082,
- },
- optimization: {
- minimize: false,
- },
- resolve: {
- modules: ['node_modules'],
- extensions: ['.ts', '.tsx', '.js', '.jsx'],
- },
- module: {
- rules: [
- {
- test: /\.(ts)x?$/,
- exclude: /node_modules/,
- use: {
- loader: 'ts-loader',
- },
- },
- {
- test: /\.(scss|css)$/,
- use: [
- {
- loader: 'style-loader',
- },
- {
- loader: 'css-loader',
- },
- {
- loader: 'postcss-loader',
- },
- {
- loader: 'sass-loader',
- options: {
- sourceMap: true,
- },
- },
- ],
- exclude: /node_modules/,
- },
- ],
- },
-
- plugins: [
- new HtmlWebpackPlugin({
- chunks: ['vendor', 'app'],
- filename: 'index.html',
- template: path.join(publicPath, '/index.html'),
- }),
- ],
-});
diff --git a/webpack.dev.js b/webpack.dev.js
new file mode 100644
index 0000000..d387b3b
--- /dev/null
+++ b/webpack.dev.js
@@ -0,0 +1,25 @@
+const { merge } = require("webpack-merge");
+const common = require("./webpack.common.js");
+
+module.exports = () => {
+ return merge(common(), {
+ output: {
+ filename: "[name].[hash].js",
+ chunkFilename: "[name].[hash].js",
+ },
+ mode: "development",
+ devtool: "source-map",
+ devServer: {
+ contentBase: "./dist",
+ historyApiFallback: true,
+ compress: true,
+ open: true,
+ hot: true,
+ overlay: true,
+ port: 8082,
+ },
+ optimization: {
+ minimize: false,
+ },
+ });
+};
diff --git a/webpack.prod.js b/webpack.prod.js
new file mode 100644
index 0000000..5c6732b
--- /dev/null
+++ b/webpack.prod.js
@@ -0,0 +1,65 @@
+const path = require("path");
+const { merge } = require("webpack-merge");
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
+const common = require("./webpack.common.js");
+
+const buildPath = path.join(__dirname, "./dist");
+
+module.exports = () => {
+ return merge(common(), {
+ mode: "production",
+ output: {
+ path: buildPath,
+ filename: "[name].[chunkhash].js",
+ chunkFilename: "[name].[chunkhash].js",
+ },
+ plugins: [
+ new MiniCssExtractPlugin({
+ filename: "[name].css",
+ chunkFilename: "[id].css",
+ }),
+ new BundleAnalyzerPlugin({
+ defaultSizes: "gzip",
+ analyzerMode: "static",
+ openAnalyzer: true,
+ }),
+ ],
+ optimization: {
+ splitChunks: {
+ cacheGroups: {
+ vendor: {
+ test: /node_modules/,
+ chunks: "initial",
+ name: "vendor",
+ },
+ },
+ },
+ },
+ module: {
+ rules: [
+ {
+ test: /app\.(sa|sc|c)ss$/,
+ use: [
+ { loader: MiniCssExtractPlugin.loader },
+ {
+ loader: "css-loader",
+ },
+ {
+ loader: "postcss-loader",
+ },
+ {
+ loader: "resolve-url-loader",
+ },
+ {
+ loader: "sass-loader",
+ options: {
+ sourceMap: true,
+ },
+ },
+ ],
+ },
+ ],
+ },
+ });
+};