mirror of
				https://gitlab.nic.cz/turris/reforis/foris-js.git
				synced 2025-11-03 23:00:31 +01:00 
			
		
		
		
	Merge branch 'fluid-aid' into 'dev'
Add auxiliary features in order to support Fluid Layout See merge request turris/reforis/foris-js!121
This commit is contained in:
		@@ -1,6 +1,8 @@
 | 
				
			|||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
    extends: "eslint-config-reforis",
 | 
					    extends: ["eslint-config-reforis", "prettier"],
 | 
				
			||||||
 | 
					    plugins: ["prettier"],
 | 
				
			||||||
    rules: {
 | 
					    rules: {
 | 
				
			||||||
 | 
					        "prettier/prettier": ["error"],
 | 
				
			||||||
        "import/prefer-default-export": "off",
 | 
					        "import/prefer-default-export": "off",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -51,3 +51,4 @@ coverage.xml
 | 
				
			|||||||
dist/
 | 
					dist/
 | 
				
			||||||
foris-*.tgz
 | 
					foris-*.tgz
 | 
				
			||||||
styleguide/
 | 
					styleguide/
 | 
				
			||||||
 | 
					testUtils
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
image: node:8-alpine
 | 
					image: node:10-alpine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
stages:
 | 
					stages:
 | 
				
			||||||
    - test
 | 
					    - test
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "singleQuote": false,
 | 
				
			||||||
 | 
					    "printWidth": 80,
 | 
				
			||||||
 | 
					    "proseWrap": "always",
 | 
				
			||||||
 | 
					    "tabWidth": 4,
 | 
				
			||||||
 | 
					    "useTabs": false,
 | 
				
			||||||
 | 
					    "trailingComma": "es5",
 | 
				
			||||||
 | 
					    "bracketSpacing": true,
 | 
				
			||||||
 | 
					    "jsxBracketSameLine": false,
 | 
				
			||||||
 | 
					    "semi": true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										16
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								README.md
									
									
									
									
									
								
							@@ -1,4 +1,5 @@
 | 
				
			|||||||
# foris-js
 | 
					# foris-js
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Set of utils and common React elements for reForis.
 | 
					Set of utils and common React elements for reForis.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Publishing package
 | 
					## Publishing package
 | 
				
			||||||
@@ -13,17 +14,20 @@ tagged `beta`. Versions names are based on commit SHA, e.g.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
1. Crete a merge request to `dev` branch with version bumped
 | 
					1. Crete a merge request to `dev` branch with version bumped
 | 
				
			||||||
2. When merging add `[skip ci]` to commit message to prevent publishing
 | 
					2. When merging add `[skip ci]` to commit message to prevent publishing
 | 
				
			||||||
unnecessary version
 | 
					   unnecessary version
 | 
				
			||||||
3. Create a merge request from `dev` to `master` branch
 | 
					3. Create a merge request from `dev` to `master` branch
 | 
				
			||||||
4. New version should be published automatically
 | 
					4. New version should be published automatically
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Manually managed dependencies
 | 
					## Manually managed dependencies
 | 
				
			||||||
Because of `<ForisForm />` component it's required to use exposed `ReactRouterDOM`
 | 
					
 | 
				
			||||||
object from `react-router-dom` library. `ReactRouterDOM` is exposed by
 | 
					Because of `<ForisForm />` component it's required to use exposed
 | 
				
			||||||
 | 
					`ReactRouterDOM` object from `react-router-dom` library. `ReactRouterDOM` is
 | 
				
			||||||
 | 
					exposed by
 | 
				
			||||||
[reForis](https://gitlab.labs.nic.cz/turris/reforis/reforis/blob/master/js/webpack.config.js).
 | 
					[reForis](https://gitlab.labs.nic.cz/turris/reforis/reforis/blob/master/js/webpack.config.js).
 | 
				
			||||||
It can be done by following steps:
 | 
					It can be done by following steps:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. Setting `react-router-dom` as `peerDependencies` and `devDependencies` in `package.json`.
 | 
					1. Setting `react-router-dom` as `peerDependencies` and `devDependencies` in
 | 
				
			||||||
 | 
					   `package.json`.
 | 
				
			||||||
2. Adding the following rules to `externals` in `webpack.conf.js` of the plugin:
 | 
					2. Adding the following rules to `externals` in `webpack.conf.js` of the plugin:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
@@ -34,11 +38,15 @@ externals: {
 | 
				
			|||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Docs
 | 
					### Docs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Build or watch docs to get more info about library:
 | 
					Build or watch docs to get more info about library:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
make docs
 | 
					make docs
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
or
 | 
					or
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
make docs-watch
 | 
					make docs-watch
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,4 @@
 | 
				
			|||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
    presets: [
 | 
					    presets: ["@babel/preset-env", "@babel/preset-react"],
 | 
				
			||||||
        "@babel/preset-env",
 | 
					    plugins: ["@babel/plugin-transform-runtime"],
 | 
				
			||||||
        "@babel/preset-react",
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    plugins: [
 | 
					 | 
				
			||||||
        "@babel/plugin-transform-runtime",
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,15 @@
 | 
				
			|||||||
Sooner or later you will face with situation when you want/need to make some changes in the library.
 | 
					Sooner or later you will face with situation when you want/need to make some
 | 
				
			||||||
Then the most important tool for you it's [`npm link`](https://docs.npmjs.com/cli/link).
 | 
					changes in the library. Then the most important tool for you it's
 | 
				
			||||||
 | 
					[`npm link`](https://docs.npmjs.com/cli/link).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Please, notice that it will not work if you link library just from root of the repo. It happens due to location of 
 | 
					Please, notice that it will not work if you link library just from root of the
 | 
				
			||||||
sources `./src`. You need to pack library first `make pack` and then link it from `./dist` directory.
 | 
					repo. It happens due to location of sources `./src`. You need to pack library
 | 
				
			||||||
 | 
					first `make pack` and then link it from `./dist` directory.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Yeah it's not such comfortable solution for development. But it can fixed by writing small script similar as `make pack`
 | 
					Yeah it's not such comfortable solution for development. But it can fixed by
 | 
				
			||||||
but with linking every file and directory from `./src` to the some directory and linking then from it. Notice that you
 | 
					writing small script similar as `make pack` but with linking every file and
 | 
				
			||||||
need to link `package.json` and `package-lock.json` as well.
 | 
					directory from `./src` to the some directory and linking then from it. Notice
 | 
				
			||||||
 | 
					that you need to link `package.json` and `package-lock.json` as well.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
So step by step:
 | 
					So step by step:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,6 @@
 | 
				
			|||||||
Foris JS library is set of components and utils for Foris JS application and plugins.
 | 
					Foris JS library is set of components and utils for Foris JS application and
 | 
				
			||||||
 | 
					plugins.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Please notice that all of these components or utils are used in reForis and plugins. If you like to study by example I would
 | 
					Please notice that all of these components or utils are used in reForis and
 | 
				
			||||||
recommend to full-text search these repos.
 | 
					plugins. If you like to study by example I would recommend to full-text search
 | 
				
			||||||
 | 
					these repos.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,7 +27,5 @@ module.exports = {
 | 
				
			|||||||
    globals: {
 | 
					    globals: {
 | 
				
			||||||
        TZ: "utc",
 | 
					        TZ: "utc",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    transformIgnorePatterns: [
 | 
					    transformIgnorePatterns: ["node_modules/(?!(react-datetime)/)"],
 | 
				
			||||||
        "node_modules/(?!(react-datetime)/)",
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										356
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										356
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    "name": "foris",
 | 
					    "name": "foris",
 | 
				
			||||||
  "version": "5.0.1",
 | 
					    "version": "5.1.0",
 | 
				
			||||||
    "lockfileVersion": 1,
 | 
					    "lockfileVersion": 1,
 | 
				
			||||||
    "requires": true,
 | 
					    "requires": true,
 | 
				
			||||||
    "dependencies": {
 | 
					    "dependencies": {
 | 
				
			||||||
@@ -3928,6 +3928,12 @@
 | 
				
			|||||||
                "@types/istanbul-lib-report": "*"
 | 
					                "@types/istanbul-lib-report": "*"
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "@types/json-schema": {
 | 
				
			||||||
 | 
					            "version": "7.0.5",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==",
 | 
				
			||||||
 | 
					            "dev": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "@types/minimatch": {
 | 
					        "@types/minimatch": {
 | 
				
			||||||
            "version": "3.0.3",
 | 
					            "version": "3.0.3",
 | 
				
			||||||
            "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
 | 
				
			||||||
@@ -6541,9 +6547,9 @@
 | 
				
			|||||||
            "dev": true
 | 
					            "dev": true
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "elliptic": {
 | 
					        "elliptic": {
 | 
				
			||||||
      "version": "6.5.2",
 | 
					            "version": "6.5.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
 | 
				
			||||||
      "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
 | 
					            "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
 | 
				
			||||||
            "dev": true,
 | 
					            "dev": true,
 | 
				
			||||||
            "requires": {
 | 
					            "requires": {
 | 
				
			||||||
                "bn.js": "^4.4.0",
 | 
					                "bn.js": "^4.4.0",
 | 
				
			||||||
@@ -6756,9 +6762,9 @@
 | 
				
			|||||||
            },
 | 
					            },
 | 
				
			||||||
            "dependencies": {
 | 
					            "dependencies": {
 | 
				
			||||||
                "eslint-scope": {
 | 
					                "eslint-scope": {
 | 
				
			||||||
          "version": "5.0.0",
 | 
					                    "version": "5.1.0",
 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
 | 
					                    "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz",
 | 
				
			||||||
          "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
 | 
					                    "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==",
 | 
				
			||||||
                    "dev": true,
 | 
					                    "dev": true,
 | 
				
			||||||
                    "requires": {
 | 
					                    "requires": {
 | 
				
			||||||
                        "esrecurse": "^4.1.0",
 | 
					                        "esrecurse": "^4.1.0",
 | 
				
			||||||
@@ -6766,37 +6772,23 @@
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "glob-parent": {
 | 
					                "glob-parent": {
 | 
				
			||||||
          "version": "5.1.0",
 | 
					                    "version": "5.1.1",
 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz",
 | 
					                    "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
 | 
				
			||||||
          "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==",
 | 
					                    "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
 | 
				
			||||||
                    "dev": true,
 | 
					                    "dev": true,
 | 
				
			||||||
                    "requires": {
 | 
					                    "requires": {
 | 
				
			||||||
                        "is-glob": "^4.0.1"
 | 
					                        "is-glob": "^4.0.1"
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "globals": {
 | 
					                "globals": {
 | 
				
			||||||
          "version": "12.3.0",
 | 
					                    "version": "12.4.0",
 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/globals/-/globals-12.3.0.tgz",
 | 
					                    "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
 | 
				
			||||||
          "integrity": "sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==",
 | 
					                    "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
 | 
				
			||||||
                    "dev": true,
 | 
					                    "dev": true,
 | 
				
			||||||
                    "requires": {
 | 
					                    "requires": {
 | 
				
			||||||
                        "type-fest": "^0.8.1"
 | 
					                        "type-fest": "^0.8.1"
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
        "optionator": {
 | 
					 | 
				
			||||||
          "version": "0.8.3",
 | 
					 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
 | 
					 | 
				
			||||||
          "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
 | 
					 | 
				
			||||||
          "dev": true,
 | 
					 | 
				
			||||||
          "requires": {
 | 
					 | 
				
			||||||
            "deep-is": "~0.1.3",
 | 
					 | 
				
			||||||
            "fast-levenshtein": "~2.0.6",
 | 
					 | 
				
			||||||
            "levn": "~0.3.0",
 | 
					 | 
				
			||||||
            "prelude-ls": "~1.1.2",
 | 
					 | 
				
			||||||
            "type-check": "~0.3.2",
 | 
					 | 
				
			||||||
            "word-wrap": "~1.2.3"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
                "semver": {
 | 
					                "semver": {
 | 
				
			||||||
                    "version": "6.3.0",
 | 
					                    "version": "6.3.0",
 | 
				
			||||||
                    "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
 | 
					                    "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
 | 
				
			||||||
@@ -6836,6 +6828,15 @@
 | 
				
			|||||||
                "object.entries": "^1.1.0"
 | 
					                "object.entries": "^1.1.0"
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "eslint-config-prettier": {
 | 
				
			||||||
 | 
					            "version": "6.11.0",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA==",
 | 
				
			||||||
 | 
					            "dev": true,
 | 
				
			||||||
 | 
					            "requires": {
 | 
				
			||||||
 | 
					                "get-stdin": "^6.0.0"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "eslint-config-reforis": {
 | 
					        "eslint-config-reforis": {
 | 
				
			||||||
            "version": "1.0.0",
 | 
					            "version": "1.0.0",
 | 
				
			||||||
            "resolved": "https://registry.npmjs.org/eslint-config-reforis/-/eslint-config-reforis-1.0.0.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/eslint-config-reforis/-/eslint-config-reforis-1.0.0.tgz",
 | 
				
			||||||
@@ -7107,6 +7108,15 @@
 | 
				
			|||||||
                "jsx-ast-utils": "^2.2.1"
 | 
					                "jsx-ast-utils": "^2.2.1"
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "eslint-plugin-prettier": {
 | 
				
			||||||
 | 
					            "version": "3.1.4",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-jZDa8z76klRqo+TdGDTFJSavwbnWK2ZpqGKNZ+VvweMW516pDUMmQ2koXvxEE4JhzNvTv+radye/bWGBmA6jmg==",
 | 
				
			||||||
 | 
					            "dev": true,
 | 
				
			||||||
 | 
					            "requires": {
 | 
				
			||||||
 | 
					                "prettier-linter-helpers": "^1.0.0"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "eslint-plugin-react": {
 | 
					        "eslint-plugin-react": {
 | 
				
			||||||
            "version": "7.16.0",
 | 
					            "version": "7.16.0",
 | 
				
			||||||
            "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.16.0.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.16.0.tgz",
 | 
				
			||||||
@@ -7167,20 +7177,20 @@
 | 
				
			|||||||
            "dev": true
 | 
					            "dev": true
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "espree": {
 | 
					        "espree": {
 | 
				
			||||||
      "version": "6.1.2",
 | 
					            "version": "6.2.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==",
 | 
					            "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==",
 | 
				
			||||||
            "dev": true,
 | 
					            "dev": true,
 | 
				
			||||||
            "requires": {
 | 
					            "requires": {
 | 
				
			||||||
        "acorn": "^7.1.0",
 | 
					                "acorn": "^7.1.1",
 | 
				
			||||||
        "acorn-jsx": "^5.1.0",
 | 
					                "acorn-jsx": "^5.2.0",
 | 
				
			||||||
                "eslint-visitor-keys": "^1.1.0"
 | 
					                "eslint-visitor-keys": "^1.1.0"
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            "dependencies": {
 | 
					            "dependencies": {
 | 
				
			||||||
        "acorn": {
 | 
					                "acorn-jsx": {
 | 
				
			||||||
          "version": "7.1.1",
 | 
					                    "version": "5.2.0",
 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
 | 
					                    "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz",
 | 
				
			||||||
          "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==",
 | 
					                    "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==",
 | 
				
			||||||
                    "dev": true
 | 
					                    "dev": true
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -7192,12 +7202,20 @@
 | 
				
			|||||||
            "dev": true
 | 
					            "dev": true
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "esquery": {
 | 
					        "esquery": {
 | 
				
			||||||
      "version": "1.0.1",
 | 
					            "version": "1.3.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==",
 | 
					            "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==",
 | 
				
			||||||
            "dev": true,
 | 
					            "dev": true,
 | 
				
			||||||
            "requires": {
 | 
					            "requires": {
 | 
				
			||||||
        "estraverse": "^4.0.0"
 | 
					                "estraverse": "^5.1.0"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "dependencies": {
 | 
				
			||||||
 | 
					                "estraverse": {
 | 
				
			||||||
 | 
					                    "version": "5.2.0",
 | 
				
			||||||
 | 
					                    "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
 | 
				
			||||||
 | 
					                    "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
 | 
				
			||||||
 | 
					                    "dev": true
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "esrecurse": {
 | 
					        "esrecurse": {
 | 
				
			||||||
@@ -7604,6 +7622,12 @@
 | 
				
			|||||||
            "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
 | 
					            "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
 | 
				
			||||||
            "dev": true
 | 
					            "dev": true
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "fast-diff": {
 | 
				
			||||||
 | 
					            "version": "1.2.0",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
 | 
				
			||||||
 | 
					            "dev": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "fast-glob": {
 | 
					        "fast-glob": {
 | 
				
			||||||
            "version": "2.2.7",
 | 
					            "version": "2.2.7",
 | 
				
			||||||
            "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz",
 | 
				
			||||||
@@ -7669,9 +7693,9 @@
 | 
				
			|||||||
            "dev": true
 | 
					            "dev": true
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "figures": {
 | 
					        "figures": {
 | 
				
			||||||
      "version": "3.1.0",
 | 
					            "version": "3.2.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==",
 | 
					            "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
 | 
				
			||||||
            "dev": true,
 | 
					            "dev": true,
 | 
				
			||||||
            "requires": {
 | 
					            "requires": {
 | 
				
			||||||
                "escape-string-regexp": "^1.0.5"
 | 
					                "escape-string-regexp": "^1.0.5"
 | 
				
			||||||
@@ -7821,9 +7845,9 @@
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "flatted": {
 | 
					        "flatted": {
 | 
				
			||||||
      "version": "2.0.1",
 | 
					            "version": "2.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==",
 | 
					            "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
 | 
				
			||||||
            "dev": true
 | 
					            "dev": true
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "flush-write-stream": {
 | 
					        "flush-write-stream": {
 | 
				
			||||||
@@ -8544,6 +8568,12 @@
 | 
				
			|||||||
            "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==",
 | 
					            "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==",
 | 
				
			||||||
            "dev": true
 | 
					            "dev": true
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "get-stdin": {
 | 
				
			||||||
 | 
					            "version": "6.0.0",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==",
 | 
				
			||||||
 | 
					            "dev": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "get-stream": {
 | 
					        "get-stream": {
 | 
				
			||||||
            "version": "4.1.0",
 | 
					            "version": "4.1.0",
 | 
				
			||||||
            "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
 | 
				
			||||||
@@ -9184,42 +9214,95 @@
 | 
				
			|||||||
            "dev": true
 | 
					            "dev": true
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "inquirer": {
 | 
					        "inquirer": {
 | 
				
			||||||
      "version": "7.0.3",
 | 
					            "version": "7.3.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.3.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz",
 | 
				
			||||||
      "integrity": "sha512-+OiOVeVydu4hnCGLCSX+wedovR/Yzskv9BFqUNNKq9uU2qg7LCcCo3R86S2E7WLo0y/x2pnEZfZe1CoYnORUAw==",
 | 
					            "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==",
 | 
				
			||||||
            "dev": true,
 | 
					            "dev": true,
 | 
				
			||||||
            "requires": {
 | 
					            "requires": {
 | 
				
			||||||
                "ansi-escapes": "^4.2.1",
 | 
					                "ansi-escapes": "^4.2.1",
 | 
				
			||||||
        "chalk": "^2.4.2",
 | 
					                "chalk": "^4.1.0",
 | 
				
			||||||
                "cli-cursor": "^3.1.0",
 | 
					                "cli-cursor": "^3.1.0",
 | 
				
			||||||
        "cli-width": "^2.0.0",
 | 
					                "cli-width": "^3.0.0",
 | 
				
			||||||
                "external-editor": "^3.0.3",
 | 
					                "external-editor": "^3.0.3",
 | 
				
			||||||
                "figures": "^3.0.0",
 | 
					                "figures": "^3.0.0",
 | 
				
			||||||
        "lodash": "^4.17.15",
 | 
					                "lodash": "^4.17.19",
 | 
				
			||||||
                "mute-stream": "0.0.8",
 | 
					                "mute-stream": "0.0.8",
 | 
				
			||||||
        "run-async": "^2.2.0",
 | 
					                "run-async": "^2.4.0",
 | 
				
			||||||
        "rxjs": "^6.5.3",
 | 
					                "rxjs": "^6.6.0",
 | 
				
			||||||
                "string-width": "^4.1.0",
 | 
					                "string-width": "^4.1.0",
 | 
				
			||||||
        "strip-ansi": "^5.1.0",
 | 
					                "strip-ansi": "^6.0.0",
 | 
				
			||||||
                "through": "^2.3.6"
 | 
					                "through": "^2.3.6"
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            "dependencies": {
 | 
					            "dependencies": {
 | 
				
			||||||
        "ansi-escapes": {
 | 
					                "ansi-styles": {
 | 
				
			||||||
          "version": "4.3.0",
 | 
					                    "version": "4.2.1",
 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz",
 | 
					                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
 | 
				
			||||||
          "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==",
 | 
					                    "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
 | 
				
			||||||
                    "dev": true,
 | 
					                    "dev": true,
 | 
				
			||||||
                    "requires": {
 | 
					                    "requires": {
 | 
				
			||||||
            "type-fest": "^0.8.1"
 | 
					                        "@types/color-name": "^1.1.1",
 | 
				
			||||||
 | 
					                        "color-convert": "^2.0.1"
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
        "strip-ansi": {
 | 
					                "chalk": {
 | 
				
			||||||
          "version": "5.2.0",
 | 
					                    "version": "4.1.0",
 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
 | 
					                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
 | 
				
			||||||
          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
 | 
					                    "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
 | 
				
			||||||
                    "dev": true,
 | 
					                    "dev": true,
 | 
				
			||||||
                    "requires": {
 | 
					                    "requires": {
 | 
				
			||||||
            "ansi-regex": "^4.1.0"
 | 
					                        "ansi-styles": "^4.1.0",
 | 
				
			||||||
 | 
					                        "supports-color": "^7.1.0"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "cli-width": {
 | 
				
			||||||
 | 
					                    "version": "3.0.0",
 | 
				
			||||||
 | 
					                    "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz",
 | 
				
			||||||
 | 
					                    "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
 | 
				
			||||||
 | 
					                    "dev": true
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "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
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "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
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "run-async": {
 | 
				
			||||||
 | 
					                    "version": "2.4.1",
 | 
				
			||||||
 | 
					                    "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
 | 
				
			||||||
 | 
					                    "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
 | 
				
			||||||
 | 
					                    "dev": true
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "rxjs": {
 | 
				
			||||||
 | 
					                    "version": "6.6.2",
 | 
				
			||||||
 | 
					                    "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz",
 | 
				
			||||||
 | 
					                    "integrity": "sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==",
 | 
				
			||||||
 | 
					                    "dev": true,
 | 
				
			||||||
 | 
					                    "requires": {
 | 
				
			||||||
 | 
					                        "tslib": "^1.9.0"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "supports-color": {
 | 
				
			||||||
 | 
					                    "version": "7.1.0",
 | 
				
			||||||
 | 
					                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
 | 
				
			||||||
 | 
					                    "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
 | 
				
			||||||
 | 
					                    "dev": true,
 | 
				
			||||||
 | 
					                    "requires": {
 | 
				
			||||||
 | 
					                        "has-flag": "^4.0.0"
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -12964,9 +13047,9 @@
 | 
				
			|||||||
            "dev": true
 | 
					            "dev": true
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "minipass": {
 | 
					        "minipass": {
 | 
				
			||||||
      "version": "3.1.1",
 | 
					            "version": "3.1.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz",
 | 
				
			||||||
      "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==",
 | 
					            "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==",
 | 
				
			||||||
            "dev": true,
 | 
					            "dev": true,
 | 
				
			||||||
            "requires": {
 | 
					            "requires": {
 | 
				
			||||||
                "yallist": "^4.0.0"
 | 
					                "yallist": "^4.0.0"
 | 
				
			||||||
@@ -12999,9 +13082,9 @@
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "minipass-pipeline": {
 | 
					        "minipass-pipeline": {
 | 
				
			||||||
      "version": "1.2.2",
 | 
					            "version": "1.2.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.2.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
 | 
				
			||||||
      "integrity": "sha512-3JS5A2DKhD2g0Gg8x3yamO0pj7YeKGwVlDS90pF++kxptwx/F+B//roxf9SqYil5tQo65bijy+dAuAFZmYOouA==",
 | 
					            "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
 | 
				
			||||||
            "dev": true,
 | 
					            "dev": true,
 | 
				
			||||||
            "requires": {
 | 
					            "requires": {
 | 
				
			||||||
                "minipass": "^3.0.0"
 | 
					                "minipass": "^3.0.0"
 | 
				
			||||||
@@ -14101,6 +14184,21 @@
 | 
				
			|||||||
            "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
 | 
					            "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
 | 
				
			||||||
            "dev": true
 | 
					            "dev": true
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "prettier": {
 | 
				
			||||||
 | 
					            "version": "2.0.5",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==",
 | 
				
			||||||
 | 
					            "dev": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "prettier-linter-helpers": {
 | 
				
			||||||
 | 
					            "version": "1.0.0",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
 | 
				
			||||||
 | 
					            "dev": true,
 | 
				
			||||||
 | 
					            "requires": {
 | 
				
			||||||
 | 
					                "fast-diff": "^1.1.2"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "pretty-format": {
 | 
					        "pretty-format": {
 | 
				
			||||||
            "version": "24.9.0",
 | 
					            "version": "24.9.0",
 | 
				
			||||||
            "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz",
 | 
				
			||||||
@@ -16627,9 +16725,9 @@
 | 
				
			|||||||
            "dev": true
 | 
					            "dev": true
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "strip-json-comments": {
 | 
					        "strip-json-comments": {
 | 
				
			||||||
      "version": "3.0.1",
 | 
					            "version": "3.1.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==",
 | 
					            "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
 | 
				
			||||||
            "dev": true
 | 
					            "dev": true
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "style-loader": {
 | 
					        "style-loader": {
 | 
				
			||||||
@@ -16801,22 +16899,34 @@
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "terser-webpack-plugin": {
 | 
					        "terser-webpack-plugin": {
 | 
				
			||||||
      "version": "2.3.5",
 | 
					            "version": "2.3.8",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.5.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.8.tgz",
 | 
				
			||||||
      "integrity": "sha512-WlWksUoq+E4+JlJ+h+U+QUzXpcsMSSNXkDy9lBVkSqDn1w23Gg29L/ary9GeJVYCGiNJJX7LnVc4bwL1N3/g1w==",
 | 
					            "integrity": "sha512-/fKw3R+hWyHfYx7Bv6oPqmk4HGQcrWLtV3X6ggvPuwPNHSnzvVV51z6OaaCOus4YLjutYGOz3pEpbhe6Up2s1w==",
 | 
				
			||||||
            "dev": true,
 | 
					            "dev": true,
 | 
				
			||||||
            "requires": {
 | 
					            "requires": {
 | 
				
			||||||
                "cacache": "^13.0.1",
 | 
					                "cacache": "^13.0.1",
 | 
				
			||||||
        "find-cache-dir": "^3.2.0",
 | 
					                "find-cache-dir": "^3.3.1",
 | 
				
			||||||
        "jest-worker": "^25.1.0",
 | 
					                "jest-worker": "^25.4.0",
 | 
				
			||||||
        "p-limit": "^2.2.2",
 | 
					                "p-limit": "^2.3.0",
 | 
				
			||||||
        "schema-utils": "^2.6.4",
 | 
					                "schema-utils": "^2.6.6",
 | 
				
			||||||
        "serialize-javascript": "^2.1.2",
 | 
					                "serialize-javascript": "^4.0.0",
 | 
				
			||||||
                "source-map": "^0.6.1",
 | 
					                "source-map": "^0.6.1",
 | 
				
			||||||
        "terser": "^4.4.3",
 | 
					                "terser": "^4.6.12",
 | 
				
			||||||
                "webpack-sources": "^1.4.3"
 | 
					                "webpack-sources": "^1.4.3"
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            "dependencies": {
 | 
					            "dependencies": {
 | 
				
			||||||
 | 
					                "ajv": {
 | 
				
			||||||
 | 
					                    "version": "6.12.4",
 | 
				
			||||||
 | 
					                    "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz",
 | 
				
			||||||
 | 
					                    "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==",
 | 
				
			||||||
 | 
					                    "dev": true,
 | 
				
			||||||
 | 
					                    "requires": {
 | 
				
			||||||
 | 
					                        "fast-deep-equal": "^3.1.1",
 | 
				
			||||||
 | 
					                        "fast-json-stable-stringify": "^2.0.0",
 | 
				
			||||||
 | 
					                        "json-schema-traverse": "^0.4.1",
 | 
				
			||||||
 | 
					                        "uri-js": "^4.2.2"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
                "cacache": {
 | 
					                "cacache": {
 | 
				
			||||||
                    "version": "13.0.1",
 | 
					                    "version": "13.0.1",
 | 
				
			||||||
                    "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz",
 | 
					                    "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz",
 | 
				
			||||||
@@ -16843,6 +16953,12 @@
 | 
				
			|||||||
                        "unique-filename": "^1.1.1"
 | 
					                        "unique-filename": "^1.1.1"
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
					                "fast-deep-equal": {
 | 
				
			||||||
 | 
					                    "version": "3.1.3",
 | 
				
			||||||
 | 
					                    "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
 | 
				
			||||||
 | 
					                    "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
 | 
				
			||||||
 | 
					                    "dev": true
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
                "find-cache-dir": {
 | 
					                "find-cache-dir": {
 | 
				
			||||||
                    "version": "3.3.1",
 | 
					                    "version": "3.3.1",
 | 
				
			||||||
                    "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz",
 | 
					                    "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz",
 | 
				
			||||||
@@ -16864,6 +16980,22 @@
 | 
				
			|||||||
                        "path-exists": "^4.0.0"
 | 
					                        "path-exists": "^4.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
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "jest-worker": {
 | 
				
			||||||
 | 
					                    "version": "25.5.0",
 | 
				
			||||||
 | 
					                    "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz",
 | 
				
			||||||
 | 
					                    "integrity": "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==",
 | 
				
			||||||
 | 
					                    "dev": true,
 | 
				
			||||||
 | 
					                    "requires": {
 | 
				
			||||||
 | 
					                        "merge-stream": "^2.0.0",
 | 
				
			||||||
 | 
					                        "supports-color": "^7.0.0"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
                "locate-path": {
 | 
					                "locate-path": {
 | 
				
			||||||
                    "version": "5.0.0",
 | 
					                    "version": "5.0.0",
 | 
				
			||||||
                    "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
 | 
					                    "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
 | 
				
			||||||
@@ -16874,18 +17006,18 @@
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "make-dir": {
 | 
					                "make-dir": {
 | 
				
			||||||
          "version": "3.0.2",
 | 
					                    "version": "3.1.0",
 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz",
 | 
					                    "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
 | 
				
			||||||
          "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==",
 | 
					                    "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
 | 
				
			||||||
                    "dev": true,
 | 
					                    "dev": true,
 | 
				
			||||||
                    "requires": {
 | 
					                    "requires": {
 | 
				
			||||||
                        "semver": "^6.0.0"
 | 
					                        "semver": "^6.0.0"
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "p-limit": {
 | 
					                "p-limit": {
 | 
				
			||||||
          "version": "2.2.2",
 | 
					                    "version": "2.3.0",
 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz",
 | 
					                    "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
 | 
				
			||||||
          "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==",
 | 
					                    "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
 | 
				
			||||||
                    "dev": true,
 | 
					                    "dev": true,
 | 
				
			||||||
                    "requires": {
 | 
					                    "requires": {
 | 
				
			||||||
                        "p-try": "^2.0.0"
 | 
					                        "p-try": "^2.0.0"
 | 
				
			||||||
@@ -16933,12 +17065,32 @@
 | 
				
			|||||||
                        "glob": "^7.1.3"
 | 
					                        "glob": "^7.1.3"
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
					                "schema-utils": {
 | 
				
			||||||
 | 
					                    "version": "2.7.0",
 | 
				
			||||||
 | 
					                    "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
 | 
				
			||||||
 | 
					                    "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
 | 
				
			||||||
 | 
					                    "dev": true,
 | 
				
			||||||
 | 
					                    "requires": {
 | 
				
			||||||
 | 
					                        "@types/json-schema": "^7.0.4",
 | 
				
			||||||
 | 
					                        "ajv": "^6.12.2",
 | 
				
			||||||
 | 
					                        "ajv-keywords": "^3.4.1"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
                "semver": {
 | 
					                "semver": {
 | 
				
			||||||
                    "version": "6.3.0",
 | 
					                    "version": "6.3.0",
 | 
				
			||||||
                    "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
 | 
					                    "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
 | 
				
			||||||
                    "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
 | 
					                    "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
 | 
				
			||||||
                    "dev": true
 | 
					                    "dev": true
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
					                "serialize-javascript": {
 | 
				
			||||||
 | 
					                    "version": "4.0.0",
 | 
				
			||||||
 | 
					                    "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
 | 
				
			||||||
 | 
					                    "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
 | 
				
			||||||
 | 
					                    "dev": true,
 | 
				
			||||||
 | 
					                    "requires": {
 | 
				
			||||||
 | 
					                        "randombytes": "^2.1.0"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
                "source-map": {
 | 
					                "source-map": {
 | 
				
			||||||
                    "version": "0.6.1",
 | 
					                    "version": "0.6.1",
 | 
				
			||||||
                    "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
 | 
					                    "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
 | 
				
			||||||
@@ -16954,6 +17106,26 @@
 | 
				
			|||||||
                        "figgy-pudding": "^3.5.1",
 | 
					                        "figgy-pudding": "^3.5.1",
 | 
				
			||||||
                        "minipass": "^3.1.1"
 | 
					                        "minipass": "^3.1.1"
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "supports-color": {
 | 
				
			||||||
 | 
					                    "version": "7.1.0",
 | 
				
			||||||
 | 
					                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
 | 
				
			||||||
 | 
					                    "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
 | 
				
			||||||
 | 
					                    "dev": true,
 | 
				
			||||||
 | 
					                    "requires": {
 | 
				
			||||||
 | 
					                        "has-flag": "^4.0.0"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "terser": {
 | 
				
			||||||
 | 
					                    "version": "4.8.0",
 | 
				
			||||||
 | 
					                    "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
 | 
				
			||||||
 | 
					                    "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
 | 
				
			||||||
 | 
					                    "dev": true,
 | 
				
			||||||
 | 
					                    "requires": {
 | 
				
			||||||
 | 
					                        "commander": "^2.20.0",
 | 
				
			||||||
 | 
					                        "source-map": "~0.6.1",
 | 
				
			||||||
 | 
					                        "source-map-support": "~0.5.12"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
@@ -17566,9 +17738,9 @@
 | 
				
			|||||||
            "dev": true
 | 
					            "dev": true
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "v8-compile-cache": {
 | 
					        "v8-compile-cache": {
 | 
				
			||||||
      "version": "2.1.0",
 | 
					            "version": "2.1.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
 | 
					            "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==",
 | 
				
			||||||
            "dev": true
 | 
					            "dev": true
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "v8-to-istanbul": {
 | 
					        "v8-to-istanbul": {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,11 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    "name": "foris",
 | 
					    "name": "foris",
 | 
				
			||||||
  "version": "5.0.1",
 | 
					    "version": "5.1.0",
 | 
				
			||||||
    "description": "Set of components and utils for Foris and its plugins.",
 | 
					    "description": "Set of components and utils for Foris and its plugins.",
 | 
				
			||||||
    "author": "CZ.NIC, z.s.p.o.",
 | 
					    "author": "CZ.NIC, z.s.p.o.",
 | 
				
			||||||
    "repository": {
 | 
					    "repository": {
 | 
				
			||||||
        "type": "git",
 | 
					        "type": "git",
 | 
				
			||||||
    "url": "https://gitlab.labs.nic.cz/turris/reforis/foris-js.git"
 | 
					        "url": "https://gitlab.nic.cz/turris/reforis/foris-js.git"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "keywords": [
 | 
					    "keywords": [
 | 
				
			||||||
        "foris",
 | 
					        "foris",
 | 
				
			||||||
@@ -41,11 +41,14 @@
 | 
				
			|||||||
        "bootstrap": "^4.5.0",
 | 
					        "bootstrap": "^4.5.0",
 | 
				
			||||||
        "css-loader": "^3.5.3",
 | 
					        "css-loader": "^3.5.3",
 | 
				
			||||||
        "eslint": "^6.8.0",
 | 
					        "eslint": "^6.8.0",
 | 
				
			||||||
 | 
					        "eslint-config-prettier": "^6.11.0",
 | 
				
			||||||
        "eslint-config-reforis": "^1.0.0",
 | 
					        "eslint-config-reforis": "^1.0.0",
 | 
				
			||||||
 | 
					        "eslint-plugin-prettier": "^3.1.4",
 | 
				
			||||||
        "file-loader": "^6.0.0",
 | 
					        "file-loader": "^6.0.0",
 | 
				
			||||||
        "jest": "^25.2.0",
 | 
					        "jest": "^25.2.0",
 | 
				
			||||||
        "jest-mock-axios": "^3.2.0",
 | 
					        "jest-mock-axios": "^3.2.0",
 | 
				
			||||||
        "moment-timezone": "^0.5.28",
 | 
					        "moment-timezone": "^0.5.28",
 | 
				
			||||||
 | 
					        "prettier": "2.0.5",
 | 
				
			||||||
        "prop-types": "15.7.2",
 | 
					        "prop-types": "15.7.2",
 | 
				
			||||||
        "react": "16.9.0",
 | 
					        "react": "16.9.0",
 | 
				
			||||||
        "react-dom": "16.9.0",
 | 
					        "react-dom": "16.9.0",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,9 +22,12 @@ function AlertContextProvider({ children }) {
 | 
				
			|||||||
    const { AlertContext } = window;
 | 
					    const { AlertContext } = window;
 | 
				
			||||||
    const [alert, setAlert] = useState(null);
 | 
					    const [alert, setAlert] = useState(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const setAlertWrapper = useCallback((message, type = ALERT_TYPES.DANGER) => {
 | 
					    const setAlertWrapper = useCallback(
 | 
				
			||||||
 | 
					        (message, type = ALERT_TYPES.DANGER) => {
 | 
				
			||||||
            setAlert({ message, type });
 | 
					            setAlert({ message, type });
 | 
				
			||||||
    }, [setAlert]);
 | 
					        },
 | 
				
			||||||
 | 
					        [setAlert]
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const dismissAlert = useCallback(() => setAlert(null), [setAlert]);
 | 
					    const dismissAlert = useCallback(() => setAlert(null), [setAlert]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -38,7 +41,7 @@ function AlertContextProvider({ children }) {
 | 
				
			|||||||
                </Portal>
 | 
					                </Portal>
 | 
				
			||||||
            )}
 | 
					            )}
 | 
				
			||||||
            <AlertContext.Provider value={[setAlertWrapper, dismissAlert]}>
 | 
					            <AlertContext.Provider value={[setAlertWrapper, dismissAlert]}>
 | 
				
			||||||
                { children }
 | 
					                {children}
 | 
				
			||||||
            </AlertContext.Provider>
 | 
					            </AlertContext.Provider>
 | 
				
			||||||
        </>
 | 
					        </>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
It provides alert context to children. `AlertContext` allows using `useAlert` in components.
 | 
					It provides alert context to children. `AlertContext` allows using `useAlert` in
 | 
				
			||||||
 | 
					components.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Notice that `<div id="alert-container"/>` should be presented in HTML doc to get it work (In reForis it's already done
 | 
					Notice that `<div id="alert-container"/>` should be presented in HTML doc to get
 | 
				
			||||||
with base Jinja2 templates).
 | 
					it work (In reForis it's already done with base Jinja2 templates).
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,9 +6,7 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import {
 | 
					import { render, getByText, queryByText, fireEvent } from "customTestRender";
 | 
				
			||||||
    render, getByText, queryByText, fireEvent,
 | 
					 | 
				
			||||||
} from "customTestRender";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { useAlert, AlertContextProvider } from "../AlertContext";
 | 
					import { useAlert, AlertContextProvider } from "../AlertContext";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -31,7 +29,7 @@ describe("AlertContext", () => {
 | 
				
			|||||||
        const { container } = render(
 | 
					        const { container } = render(
 | 
				
			||||||
            <AlertContextProvider>
 | 
					            <AlertContextProvider>
 | 
				
			||||||
                <AlertTest />
 | 
					                <AlertTest />
 | 
				
			||||||
            </AlertContextProvider>,
 | 
					            </AlertContextProvider>
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        componentContainer = container;
 | 
					        componentContainer = container;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,13 +5,16 @@
 | 
				
			|||||||
 * See /LICENSE for more information.
 | 
					 * See /LICENSE for more information.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import { useCallback, useEffect, useReducer, useState } from "react";
 | 
				
			||||||
    useCallback, useEffect, useReducer, useState,
 | 
					 | 
				
			||||||
} from "react";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { ForisURLs } from "../utils/forisUrls";
 | 
					import { ForisURLs } from "../utils/forisUrls";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    API_ACTIONS, API_METHODS, API_STATE, getErrorPayload, HEADERS, TIMEOUT,
 | 
					    API_ACTIONS,
 | 
				
			||||||
 | 
					    API_METHODS,
 | 
				
			||||||
 | 
					    API_STATE,
 | 
				
			||||||
 | 
					    getErrorPayload,
 | 
				
			||||||
 | 
					    HEADERS,
 | 
				
			||||||
 | 
					    TIMEOUT,
 | 
				
			||||||
} from "./utils";
 | 
					} from "./utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DATA_METHODS = ["POST", "PATCH", "PUT"];
 | 
					const DATA_METHODS = ["POST", "PATCH", "PUT"];
 | 
				
			||||||
@@ -23,7 +26,8 @@ function createAPIHook(method) {
 | 
				
			|||||||
            data: null,
 | 
					            data: null,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const sendRequest = useCallback(async ({ data, suffix } = {}) => {
 | 
					        const sendRequest = useCallback(
 | 
				
			||||||
 | 
					            async ({ data, suffix } = {}) => {
 | 
				
			||||||
                const headers = { ...HEADERS };
 | 
					                const headers = { ...HEADERS };
 | 
				
			||||||
                if (contentType) {
 | 
					                if (contentType) {
 | 
				
			||||||
                    headers["Content-Type"] = contentType;
 | 
					                    headers["Content-Type"] = contentType;
 | 
				
			||||||
@@ -60,7 +64,9 @@ function createAPIHook(method) {
 | 
				
			|||||||
                        payload: errorPayload,
 | 
					                        payload: errorPayload,
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
        }, [urlRoot, contentType]);
 | 
					            },
 | 
				
			||||||
 | 
					            [urlRoot, contentType]
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        return [state, sendRequest];
 | 
					        return [state, sendRequest];
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -83,8 +89,12 @@ function APIReducer(state, action) {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Not an API error - should be rethrown.
 | 
					            // Not an API error - should be rethrown.
 | 
				
			||||||
        if (action.payload && action.payload.stack && action.payload.message) {
 | 
					            if (
 | 
				
			||||||
            throw (action.payload);
 | 
					                action.payload &&
 | 
				
			||||||
 | 
					                action.payload.stack &&
 | 
				
			||||||
 | 
					                action.payload.message
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                throw action.payload;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
@@ -102,11 +112,10 @@ const useAPIPatch = createAPIHook("PATCH");
 | 
				
			|||||||
const useAPIPut = createAPIHook("PUT");
 | 
					const useAPIPut = createAPIHook("PUT");
 | 
				
			||||||
const useAPIDelete = createAPIHook("DELETE");
 | 
					const useAPIDelete = createAPIHook("DELETE");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export {
 | 
					export { useAPIGet, useAPIPost, useAPIPatch, useAPIPut, useAPIDelete };
 | 
				
			||||||
    useAPIGet, useAPIPost, useAPIPatch, useAPIPut, useAPIDelete,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function useAPIPolling(endpoint, delay = 1000, until) { // delay ms
 | 
					export function useAPIPolling(endpoint, delay = 1000, until) {
 | 
				
			||||||
 | 
					    // delay ms
 | 
				
			||||||
    const [state, setState] = useState({ state: API_STATE.INIT });
 | 
					    const [state, setState] = useState({ state: API_STATE.INIT });
 | 
				
			||||||
    const [getResponse, get] = useAPIGet(endpoint);
 | 
					    const [getResponse, get] = useAPIGet(endpoint);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,8 +43,10 @@ function getCookie(name) {
 | 
				
			|||||||
        for (let i = 0; i < cookies.length; i++) {
 | 
					        for (let i = 0; i < cookies.length; i++) {
 | 
				
			||||||
            const cookie = cookies[i].trim();
 | 
					            const cookie = cookies[i].trim();
 | 
				
			||||||
            // Does this cookie string begin with the name we want?
 | 
					            // Does this cookie string begin with the name we want?
 | 
				
			||||||
            if (cookie.substring(0, name.length + 1) === (`${name}=`)) {
 | 
					            if (cookie.substring(0, name.length + 1) === `${name}=`) {
 | 
				
			||||||
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
 | 
					                cookieValue = decodeURIComponent(
 | 
				
			||||||
 | 
					                    cookie.substring(name.length + 1)
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,12 +35,16 @@ Alert.defaultProps = {
 | 
				
			|||||||
    type: ALERT_TYPES.DANGER,
 | 
					    type: ALERT_TYPES.DANGER,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Alert({
 | 
					export function Alert({ type, onDismiss, children }) {
 | 
				
			||||||
    type, onDismiss, children,
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <div className={`alert alert-dismissible alert-${type}`}>
 | 
					        <div className={`alert alert-dismissible alert-${type}`}>
 | 
				
			||||||
            {onDismiss ? <button type="button" className="close" onClick={onDismiss}>×</button> : false}
 | 
					            {onDismiss ? (
 | 
				
			||||||
 | 
					                <button type="button" className="close" onClick={onDismiss}>
 | 
				
			||||||
 | 
					                    ×
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            ) : (
 | 
				
			||||||
 | 
					                false
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
            {children}
 | 
					            {children}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,21 +1,21 @@
 | 
				
			|||||||
Bootstrap alert component.
 | 
					Bootstrap alert component.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```jsx
 | 
					```jsx
 | 
				
			||||||
import {useState} from 'react';
 | 
					import { useState } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function AlertExample(){
 | 
					function AlertExample() {
 | 
				
			||||||
    const [alert, setAlert] = useState(true);
 | 
					    const [alert, setAlert] = useState(true);
 | 
				
			||||||
    if (alert)
 | 
					    if (alert)
 | 
				
			||||||
        return <Alert 
 | 
					        return (
 | 
				
			||||||
            type='warning' 
 | 
					            <Alert type="warning" onDismiss={() => setAlert(false)}>
 | 
				
			||||||
            onDismiss={()=>setAlert(false)}
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
                Some warning out there!
 | 
					                Some warning out there!
 | 
				
			||||||
        </Alert>;
 | 
					            </Alert>
 | 
				
			||||||
    return <button 
 | 
					        );
 | 
				
			||||||
        className='btn btn-secondary' 
 | 
					    return (
 | 
				
			||||||
        onClick={()=>setAlert(true)}
 | 
					        <button className="btn btn-secondary" onClick={() => setAlert(true)}>
 | 
				
			||||||
    >Show alert again</button>;
 | 
					            Show alert again
 | 
				
			||||||
};
 | 
					        </button>
 | 
				
			||||||
<AlertExample/>
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					<AlertExample />;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,22 +25,29 @@ Button.propTypes = {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Button({
 | 
					export function Button({
 | 
				
			||||||
    className, loading, forisFormSize, children, ...props
 | 
					    className,
 | 
				
			||||||
 | 
					    loading,
 | 
				
			||||||
 | 
					    forisFormSize,
 | 
				
			||||||
 | 
					    children,
 | 
				
			||||||
 | 
					    ...props
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
    let buttonClass = className ? `btn ${className}` : "btn btn-primary ";
 | 
					    let buttonClass = className ? `btn ${className}` : "btn btn-primary ";
 | 
				
			||||||
    if (forisFormSize) {
 | 
					    if (forisFormSize) {
 | 
				
			||||||
        buttonClass = `${buttonClass} col-sm-12 col-lg-3`;
 | 
					        buttonClass = `${buttonClass} col-sm-12 col-md-3 col-lg-2`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const span = loading
 | 
					    const span = loading ? (
 | 
				
			||||||
        ? <span className="spinner-border spinner-border-sm" role="status" aria-hidden="true" /> : null;
 | 
					        <span
 | 
				
			||||||
 | 
					            className="spinner-border spinner-border-sm"
 | 
				
			||||||
 | 
					            role="status"
 | 
				
			||||||
 | 
					            aria-hidden="true"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    ) : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <button type="button" className={buttonClass} {...props}>
 | 
					        <button type="button" className={buttonClass} {...props}>
 | 
				
			||||||
            {span}
 | 
					            {span}
 | 
				
			||||||
            {" "}
 | 
					 | 
				
			||||||
            {span ? " " : null}
 | 
					            {span ? " " : null}
 | 
				
			||||||
            {" "}
 | 
					 | 
				
			||||||
            {children}
 | 
					            {children}
 | 
				
			||||||
        </button>
 | 
					        </button>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,5 +11,7 @@ Can be used without parameters:
 | 
				
			|||||||
Using loading spinner:
 | 
					Using loading spinner:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```jsx
 | 
					```jsx
 | 
				
			||||||
<Button loading disabled>Loading...</Button>
 | 
					<Button loading disabled>
 | 
				
			||||||
 | 
					    Loading...
 | 
				
			||||||
 | 
					</Button>
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,9 +22,7 @@ CheckBox.defaultProps = {
 | 
				
			|||||||
    disabled: false,
 | 
					    disabled: false,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function CheckBox({
 | 
					export function CheckBox({ label, helpText, disabled, ...props }) {
 | 
				
			||||||
    label, helpText, disabled, ...props
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
    const uid = useUID();
 | 
					    const uid = useUID();
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <div className="form-group">
 | 
					        <div className="form-group">
 | 
				
			||||||
@@ -34,12 +32,15 @@ export function CheckBox({
 | 
				
			|||||||
                    type="checkbox"
 | 
					                    type="checkbox"
 | 
				
			||||||
                    id={uid}
 | 
					                    id={uid}
 | 
				
			||||||
                    disabled={disabled}
 | 
					                    disabled={disabled}
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    {...props}
 | 
					                    {...props}
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                <label className="custom-control-label" htmlFor={uid}>
 | 
					                <label className="custom-control-label" htmlFor={uid}>
 | 
				
			||||||
                    {label}
 | 
					                    {label}
 | 
				
			||||||
                    {helpText && <small className="form-text text-muted">{helpText}</small>}
 | 
					                    {helpText && (
 | 
				
			||||||
 | 
					                        <small className="form-text text-muted">
 | 
				
			||||||
 | 
					                            {helpText}
 | 
				
			||||||
 | 
					                        </small>
 | 
				
			||||||
 | 
					                    )}
 | 
				
			||||||
                </label>
 | 
					                </label>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,16 +1,17 @@
 | 
				
			|||||||
Checkbox with label Bootstrap component with predefined sizes and structure for using in foris forms.
 | 
					Checkbox with label Bootstrap component with predefined sizes and structure for
 | 
				
			||||||
 | 
					using in foris forms.
 | 
				
			||||||
All additional `props` are passed to the `<input type="checkbox">` HTML component.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					All additional `props` are passed to the `<input type="checkbox">` HTML
 | 
				
			||||||
 | 
					component.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
import {useState} from 'react';
 | 
					import { useState } from "react";
 | 
				
			||||||
const [value, setValue] = useState(false);
 | 
					const [value, setValue] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<CheckBox
 | 
					<CheckBox
 | 
				
			||||||
    value={value}
 | 
					    value={value}
 | 
				
			||||||
    label="Some label"
 | 
					    label="Some label"
 | 
				
			||||||
    helpText="Read the small text!"
 | 
					    helpText="Read the small text!"
 | 
				
			||||||
    onChange={event =>setValue(event.target.value)}
 | 
					    onChange={(event) => setValue(event.target.value)}
 | 
				
			||||||
/>
 | 
					/>;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,14 +38,17 @@ const DEFAULT_DATE_FORMAT = "YYYY-MM-DD";
 | 
				
			|||||||
const DEFAULT_TIME_FORMAT = "HH:mm:ss";
 | 
					const DEFAULT_TIME_FORMAT = "HH:mm:ss";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function DataTimeInput({
 | 
					export function DataTimeInput({
 | 
				
			||||||
    value, onChange, isValidDate, dateFormat, timeFormat, children, ...props
 | 
					    value,
 | 
				
			||||||
 | 
					    onChange,
 | 
				
			||||||
 | 
					    isValidDate,
 | 
				
			||||||
 | 
					    dateFormat,
 | 
				
			||||||
 | 
					    timeFormat,
 | 
				
			||||||
 | 
					    children,
 | 
				
			||||||
 | 
					    ...props
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
    function renderInput(datetimeProps) {
 | 
					    function renderInput(datetimeProps) {
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            <Input
 | 
					            <Input {...props} {...datetimeProps}>
 | 
				
			||||||
                {...props}
 | 
					 | 
				
			||||||
                {...datetimeProps}
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
                {children}
 | 
					                {children}
 | 
				
			||||||
            </Input>
 | 
					            </Input>
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
@@ -54,8 +57,12 @@ export function DataTimeInput({
 | 
				
			|||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Datetime
 | 
					        <Datetime
 | 
				
			||||||
            locale={ForisTranslations.locale}
 | 
					            locale={ForisTranslations.locale}
 | 
				
			||||||
            dateFormat={dateFormat !== undefined ? dateFormat : DEFAULT_DATE_FORMAT}
 | 
					            dateFormat={
 | 
				
			||||||
            timeFormat={timeFormat !== undefined ? timeFormat : DEFAULT_TIME_FORMAT}
 | 
					                dateFormat !== undefined ? dateFormat : DEFAULT_DATE_FORMAT
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            timeFormat={
 | 
				
			||||||
 | 
					                timeFormat !== undefined ? timeFormat : DEFAULT_TIME_FORMAT
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            value={value}
 | 
					            value={value}
 | 
				
			||||||
            onChange={onChange}
 | 
					            onChange={onChange}
 | 
				
			||||||
            isValidDate={isValidDate}
 | 
					            isValidDate={isValidDate}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,25 +1,26 @@
 | 
				
			|||||||
Adopted from `react-datetime/DateTime` datatime picker component.
 | 
					Adopted from `react-datetime/DateTime` datatime picker component. It uses
 | 
				
			||||||
It uses `momentjs` see example.
 | 
					`momentjs` see example.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
It requires `ForisTranslations.locale` to be defined in order to use right locale.
 | 
					It requires `ForisTranslations.locale` to be defined in order to use right
 | 
				
			||||||
 | 
					locale.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
ForisTranslations={locale:'en'};
 | 
					ForisTranslations = { locale: "en" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {useState, useEffect} from 'react';
 | 
					import { useState, useEffect } from "react";
 | 
				
			||||||
import moment from 'moment/moment';
 | 
					import moment from "moment/moment";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const [dataTime, setDataTime] = useState(moment());
 | 
					const [dataTime, setDataTime] = useState(moment());
 | 
				
			||||||
const [error, setError] = useState();
 | 
					const [error, setError] = useState();
 | 
				
			||||||
useEffect(()=>{
 | 
					useEffect(() => {
 | 
				
			||||||
   dataTime.isValid() ? setError(null) : setError('Invalid value!');
 | 
					    dataTime.isValid() ? setError(null) : setError("Invalid value!");
 | 
				
			||||||
},[dataTime]);
 | 
					}, [dataTime]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<DataTimeInput
 | 
					<DataTimeInput
 | 
				
			||||||
    label='Time to sleep'
 | 
					    label="Time to sleep"
 | 
				
			||||||
    value={dataTime}
 | 
					    value={dataTime}
 | 
				
			||||||
    error={error}
 | 
					    error={error}
 | 
				
			||||||
    helpText='Example helptext...'
 | 
					    helpText="Example helptext..."
 | 
				
			||||||
    onChange={value => setDataTime(value)}
 | 
					    onChange={(value) => setDataTime(value)}
 | 
				
			||||||
/>
 | 
					/>;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,11 +23,7 @@ DownloadButton.defaultProps = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export function DownloadButton({ href, className, children }) {
 | 
					export function DownloadButton({ href, className, children }) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <a
 | 
					        <a href={href} className={`btn ${className}`.trim()} download>
 | 
				
			||||||
            href={href}
 | 
					 | 
				
			||||||
            className={`btn ${className}`.trim()}
 | 
					 | 
				
			||||||
            download
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
            {children}
 | 
					            {children}
 | 
				
			||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,9 @@
 | 
				
			|||||||
Hyperlink with apperance of a button.
 | 
					Hyperlink with apperance of a button.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
It has `download` attribute, which prevents closing WebSocket connection on Firefox. See [related issue](https://bugzilla.mozilla.org/show_bug.cgi?id=858538) for more details.
 | 
					It has `download` attribute, which prevents closing WebSocket connection on
 | 
				
			||||||
 | 
					Firefox. See
 | 
				
			||||||
 | 
					[related issue](https://bugzilla.mozilla.org/show_bug.cgi?id=858538) for more
 | 
				
			||||||
 | 
					details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
<DownloadButton href="example.zip">Download</DownloadButton>
 | 
					<DownloadButton href="example.zip">Download</DownloadButton>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,18 +1,19 @@
 | 
				
			|||||||
Bootstrap component of email input with label with predefined sizes and structure for using in foris forms.
 | 
					Bootstrap component of email input with label with predefined sizes and
 | 
				
			||||||
It use built-in browser email address checking. It's only meaningful using inside `<form>`.
 | 
					structure for using in foris forms. It use built-in browser email address
 | 
				
			||||||
 | 
					checking. It's only meaningful using inside `<form>`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
All additional `props` are passed to the `<input type="email">` HTML component.
 | 
					All additional `props` are passed to the `<input type="email">` HTML component.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
import {useState} from 'react';
 | 
					import { useState } from "react";
 | 
				
			||||||
const [email, setEmail] = useState('Wrong email');
 | 
					const [email, setEmail] = useState("Wrong email");
 | 
				
			||||||
<form onSubmit={e=>e.preventDefault()}>
 | 
					<form onSubmit={(e) => e.preventDefault()}>
 | 
				
			||||||
    <EmailInput
 | 
					    <EmailInput
 | 
				
			||||||
        value={email}
 | 
					        value={email}
 | 
				
			||||||
        label="Some label"
 | 
					        label="Some label"
 | 
				
			||||||
        helpText="Read the small text!"
 | 
					        helpText="Read the small text!"
 | 
				
			||||||
        onChange={event =>setEmail(event.target.value)}
 | 
					        onChange={(event) => setEmail(event.target.value)}
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
    <button type="submit">Try to submit</button>
 | 
					    <button type="submit">Try to submit</button>
 | 
				
			||||||
</form>
 | 
					</form>;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,10 @@
 | 
				
			|||||||
Bootstrap component for file input. Includes label and has predefined sizes and structure for using in foris forms. 
 | 
					Bootstrap component for file input. Includes label and has predefined sizes and
 | 
				
			||||||
 | 
					structure for using in foris forms.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
All additional `props` are passed to the `<input type="file">` HTML component.
 | 
					All additional `props` are passed to the `<input type="file">` HTML component.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
import { useState } from 'react';
 | 
					import { useState } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const [files, setFiles] = useState([]);
 | 
					const [files, setFiles] = useState([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -15,27 +16,33 @@ const label = files.length === 1 ? files[0].name : "Choose file";
 | 
				
			|||||||
        files={files}
 | 
					        files={files}
 | 
				
			||||||
        label={label}
 | 
					        label={label}
 | 
				
			||||||
        helpText="Will be uploaded"
 | 
					        helpText="Will be uploaded"
 | 
				
			||||||
        onChange={event=>setFiles(event.target.files)}
 | 
					        onChange={(event) => setFiles(event.target.files)}
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
</form>
 | 
					</form>;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### FileInput with multiple files
 | 
					### FileInput with multiple files
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
import { useState } from 'react';
 | 
					import { useState } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const [files, setFiles] = useState([]);
 | 
					const [files, setFiles] = useState([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Note that files is not an array but FileList.
 | 
					// Note that files is not an array but FileList.
 | 
				
			||||||
const label = files.length > 0 ? Array.from(files).map(file=>file.name).join(", ") : "Choose files";
 | 
					const label =
 | 
				
			||||||
 | 
					    files.length > 0
 | 
				
			||||||
 | 
					        ? Array.from(files)
 | 
				
			||||||
 | 
					              .map((file) => file.name)
 | 
				
			||||||
 | 
					              .join(", ")
 | 
				
			||||||
 | 
					        : "Choose files";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<form className="col">
 | 
					<form className="col">
 | 
				
			||||||
    <FileInput
 | 
					    <FileInput
 | 
				
			||||||
        files={files}
 | 
					        files={files}
 | 
				
			||||||
        label={label}
 | 
					        label={label}
 | 
				
			||||||
        helpText="Will be uploaded"
 | 
					        helpText="Will be uploaded"
 | 
				
			||||||
        onChange={event=>setFiles(event.target.files)}
 | 
					        onChange={(event) => setFiles(event.target.files)}
 | 
				
			||||||
        multiple
 | 
					        multiple
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
</form>
 | 
					</form>;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,25 +25,38 @@ Input.propTypes = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/** Base bootstrap input component. */
 | 
					/** Base bootstrap input component. */
 | 
				
			||||||
export function Input({
 | 
					export function Input({
 | 
				
			||||||
    type, label, helpText, error, className, children, labelClassName, groupClassName, ...props
 | 
					    type,
 | 
				
			||||||
 | 
					    label,
 | 
				
			||||||
 | 
					    helpText,
 | 
				
			||||||
 | 
					    error,
 | 
				
			||||||
 | 
					    className,
 | 
				
			||||||
 | 
					    children,
 | 
				
			||||||
 | 
					    labelClassName,
 | 
				
			||||||
 | 
					    groupClassName,
 | 
				
			||||||
 | 
					    ...props
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
    const uid = useUID();
 | 
					    const uid = useUID();
 | 
				
			||||||
    const inputClassName = `form-control ${className || ""} ${(error ? "is-invalid" : "")}`.trim();
 | 
					    const inputClassName = `form-control ${className || ""} ${
 | 
				
			||||||
 | 
					        error ? "is-invalid" : ""
 | 
				
			||||||
 | 
					    }`.trim();
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <div className="form-group">
 | 
					        <div className="form-group">
 | 
				
			||||||
            <label className={labelClassName} htmlFor={uid}>{label}</label>
 | 
					            <label className={labelClassName} htmlFor={uid}>
 | 
				
			||||||
 | 
					                {label}
 | 
				
			||||||
 | 
					            </label>
 | 
				
			||||||
            <div className={`input-group ${groupClassName || ""}`.trim()}>
 | 
					            <div className={`input-group ${groupClassName || ""}`.trim()}>
 | 
				
			||||||
                <input
 | 
					                <input
 | 
				
			||||||
                    className={inputClassName}
 | 
					                    className={inputClassName}
 | 
				
			||||||
                    type={type}
 | 
					                    type={type}
 | 
				
			||||||
                    id={uid}
 | 
					                    id={uid}
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    {...props}
 | 
					                    {...props}
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                {children}
 | 
					                {children}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            {error ? <div className="invalid-feedback">{error}</div> : null}
 | 
					            {error ? <div className="invalid-feedback">{error}</div> : null}
 | 
				
			||||||
            {helpText ? <small className="form-text text-muted">{helpText}</small> : null}
 | 
					            {helpText ? (
 | 
				
			||||||
 | 
					                <small className="form-text text-muted">{helpText}</small>
 | 
				
			||||||
 | 
					            ) : null}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,6 @@
 | 
				
			|||||||
.modal.show {
 | 
					.modal.show {
 | 
				
			||||||
    display: block;
 | 
					    display: block;
 | 
				
			||||||
    animation-name: modalFade;
 | 
					    animation-name: modalFade;
 | 
				
			||||||
    animation-duration: .3s;
 | 
					    animation-duration: 0.3s;
 | 
				
			||||||
    background: rgba(0, 0, 0, 0.2);
 | 
					    background: rgba(0, 0, 0, 0.2);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,9 +26,7 @@ Modal.propTypes = {
 | 
				
			|||||||
    ]).isRequired,
 | 
					    ]).isRequired,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Modal({
 | 
					export function Modal({ shown, setShown, scrollable, children }) {
 | 
				
			||||||
    shown, setShown, scrollable, children,
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
    const dialogRef = useRef();
 | 
					    const dialogRef = useRef();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useClickOutside(dialogRef, () => setShown(false));
 | 
					    useClickOutside(dialogRef, () => setShown(false));
 | 
				
			||||||
@@ -38,12 +36,12 @@ export function Modal({
 | 
				
			|||||||
            <div className={`modal fade ${shown ? "show" : ""}`} role="dialog">
 | 
					            <div className={`modal fade ${shown ? "show" : ""}`} role="dialog">
 | 
				
			||||||
                <div
 | 
					                <div
 | 
				
			||||||
                    ref={dialogRef}
 | 
					                    ref={dialogRef}
 | 
				
			||||||
                    className={`modal-dialog modal-dialog-centered${scrollable ? " modal-dialog-scrollable" : ""}`}
 | 
					                    className={`modal-dialog modal-dialog-centered${
 | 
				
			||||||
 | 
					                        scrollable ? " modal-dialog-scrollable" : ""
 | 
				
			||||||
 | 
					                    }`}
 | 
				
			||||||
                    role="document"
 | 
					                    role="document"
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                    <div className="modal-content">
 | 
					                    <div className="modal-content">{children}</div>
 | 
				
			||||||
                        {children}
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </Portal>
 | 
					        </Portal>
 | 
				
			||||||
@@ -59,7 +57,11 @@ export function ModalHeader({ setShown, title }) {
 | 
				
			|||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <div className="modal-header">
 | 
					        <div className="modal-header">
 | 
				
			||||||
            <h5 className="modal-title">{title}</h5>
 | 
					            <h5 className="modal-title">{title}</h5>
 | 
				
			||||||
            <button type="button" className="close" onClick={() => setShown(false)}>
 | 
					            <button
 | 
				
			||||||
 | 
					                type="button"
 | 
				
			||||||
 | 
					                className="close"
 | 
				
			||||||
 | 
					                onClick={() => setShown(false)}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
                <span aria-hidden="true">×</span>
 | 
					                <span aria-hidden="true">×</span>
 | 
				
			||||||
            </button>
 | 
					            </button>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
@@ -85,9 +87,5 @@ ModalFooter.propTypes = {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function ModalFooter({ children }) {
 | 
					export function ModalFooter({ children }) {
 | 
				
			||||||
    return (
 | 
					    return <div className="modal-footer">{children}</div>;
 | 
				
			||||||
        <div className="modal-footer">
 | 
					 | 
				
			||||||
            {children}
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,31 +1,36 @@
 | 
				
			|||||||
Bootstrap modal component.
 | 
					Bootstrap modal component.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
it's required to have an element `<div id={"modal-container"}/>` somewhere on the page since modals are rendered in portals.
 | 
					it's required to have an element `<div id={"modal-container"}/>` somewhere on
 | 
				
			||||||
 | 
					the page since modals are rendered in portals.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
    <div id="modal-container"/>
 | 
					<div id="modal-container" />
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
import {ModalHeader, ModalBody, ModalFooter} from './Modal';
 | 
					import { ModalHeader, ModalBody, ModalFooter } from "./Modal";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {useState} from 'react';
 | 
					import { useState } from "react";
 | 
				
			||||||
const [shown, setShown] = useState(false);
 | 
					const [shown, setShown] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<>
 | 
					<>
 | 
				
			||||||
    <Modal setShown={setShown} shown={shown}>
 | 
					    <Modal setShown={setShown} shown={shown}>
 | 
				
			||||||
        <ModalHeader setShown={setShown} title='Warning!'/>
 | 
					        <ModalHeader setShown={setShown} title="Warning!" />
 | 
				
			||||||
        <ModalBody><p>Bla bla bla...</p></ModalBody>
 | 
					        <ModalBody>
 | 
				
			||||||
 | 
					            <p>Bla bla bla...</p>
 | 
				
			||||||
 | 
					        </ModalBody>
 | 
				
			||||||
        <ModalFooter>
 | 
					        <ModalFooter>
 | 
				
			||||||
            <button
 | 
					            <button
 | 
				
			||||||
                className='btn btn-secondary' 
 | 
					                className="btn btn-secondary"
 | 
				
			||||||
                onClick={() => setShown(false)}
 | 
					                onClick={() => setShown(false)}
 | 
				
			||||||
            >Skip it</button>
 | 
					            >
 | 
				
			||||||
 | 
					                Skip it
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
        </ModalFooter>
 | 
					        </ModalFooter>
 | 
				
			||||||
    </Modal>
 | 
					    </Modal>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <button className='btn btn-secondary' onClick={()=>setShown(true)}>
 | 
					    <button className="btn btn-secondary" onClick={() => setShown(true)}>
 | 
				
			||||||
        Show modal
 | 
					        Show modal
 | 
				
			||||||
    </button>
 | 
					    </button>
 | 
				
			||||||
</>
 | 
					</>;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@ input[type="number"] {
 | 
				
			|||||||
    appearance: textfield;
 | 
					    appearance: textfield;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
input[type=number]::-webkit-inner-spin-button,
 | 
					input[type="number"]::-webkit-inner-spin-button,
 | 
				
			||||||
input[type=number]::-webkit-outer-spin-button {
 | 
					input[type="number"]::-webkit-outer-spin-button {
 | 
				
			||||||
    -webkit-appearance: none;
 | 
					    -webkit-appearance: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,10 +20,7 @@ NumberInput.propTypes = {
 | 
				
			|||||||
    /** Help text message. */
 | 
					    /** Help text message. */
 | 
				
			||||||
    helpText: PropTypes.string,
 | 
					    helpText: PropTypes.string,
 | 
				
			||||||
    /** Number value. */
 | 
					    /** Number value. */
 | 
				
			||||||
    value: PropTypes.oneOfType([
 | 
					    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
 | 
				
			||||||
        PropTypes.string,
 | 
					 | 
				
			||||||
        PropTypes.number,
 | 
					 | 
				
			||||||
    ]),
 | 
					 | 
				
			||||||
    /** Function called when value changes. */
 | 
					    /** Function called when value changes. */
 | 
				
			||||||
    onChange: PropTypes.func.isRequired,
 | 
					    onChange: PropTypes.func.isRequired,
 | 
				
			||||||
    /** Additional description dispaled to the right of input value. */
 | 
					    /** Additional description dispaled to the right of input value. */
 | 
				
			||||||
@@ -34,15 +31,21 @@ NumberInput.defaultProps = {
 | 
				
			|||||||
    value: 0,
 | 
					    value: 0,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function NumberInput({
 | 
					export function NumberInput({ onChange, inlineText, value, ...props }) {
 | 
				
			||||||
    onChange, inlineText, value, ...props
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
    function updateValue(initialValue, difference) {
 | 
					    function updateValue(initialValue, difference) {
 | 
				
			||||||
        onChange({ target: { value: initialValue + difference } });
 | 
					        onChange({ target: { value: initialValue + difference } });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const enableIncrease = useConditionalTimeout({ callback: updateValue }, value, 1);
 | 
					    const enableIncrease = useConditionalTimeout(
 | 
				
			||||||
    const enableDecrease = useConditionalTimeout({ callback: updateValue }, value, -1);
 | 
					        { callback: updateValue },
 | 
				
			||||||
 | 
					        value,
 | 
				
			||||||
 | 
					        1
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    const enableDecrease = useConditionalTimeout(
 | 
				
			||||||
 | 
					        { callback: updateValue },
 | 
				
			||||||
 | 
					        value,
 | 
				
			||||||
 | 
					        -1
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Input type="number" onChange={onChange} value={value} {...props}>
 | 
					        <Input type="number" onChange={onChange} value={value} {...props}>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,18 @@
 | 
				
			|||||||
Bootstrap component of number input with label with predefined sizes and structure for using in foris forms.
 | 
					Bootstrap component of number input with label with predefined sizes and
 | 
				
			||||||
 | 
					structure for using in foris forms.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
All additional `props` are passed to the `<input type="number">` HTML component.
 | 
					All additional `props` are passed to the `<input type="number">` HTML component.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
import {useState} from 'react';
 | 
					import { useState } from "react";
 | 
				
			||||||
const [value, setValue] = useState(42);
 | 
					const [value, setValue] = useState(42);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<NumberInput
 | 
					<NumberInput
 | 
				
			||||||
    value={value}
 | 
					    value={value}
 | 
				
			||||||
    label="Some number"
 | 
					    label="Some number"
 | 
				
			||||||
    helpText="Read the small text!"
 | 
					    helpText="Read the small text!"
 | 
				
			||||||
    min='33'
 | 
					    min="33"
 | 
				
			||||||
    max='54'
 | 
					    max="54"
 | 
				
			||||||
    onChange={event =>setValue(event.target.value)}
 | 
					    onChange={(event) => setValue(event.target.value)}
 | 
				
			||||||
/>
 | 
					/>;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,8 +31,7 @@ export function PasswordInput({ withEye, ...props }) {
 | 
				
			|||||||
            autoComplete={isHidden ? "new-password" : null}
 | 
					            autoComplete={isHidden ? "new-password" : null}
 | 
				
			||||||
            {...props}
 | 
					            {...props}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
            {withEye
 | 
					            {withEye ? (
 | 
				
			||||||
                ? (
 | 
					 | 
				
			||||||
                <div className="input-group-append">
 | 
					                <div className="input-group-append">
 | 
				
			||||||
                    <button
 | 
					                    <button
 | 
				
			||||||
                        type="button"
 | 
					                        type="button"
 | 
				
			||||||
@@ -42,11 +41,14 @@ export function PasswordInput({ withEye, ...props }) {
 | 
				
			|||||||
                            setHidden((shouldBeHidden) => !shouldBeHidden);
 | 
					                            setHidden((shouldBeHidden) => !shouldBeHidden);
 | 
				
			||||||
                        }}
 | 
					                        }}
 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
                            <i className={`fa ${isHidden ? "fa-eye" : "fa-eye-slash"}`} />
 | 
					                        <i
 | 
				
			||||||
 | 
					                            className={`fa ${
 | 
				
			||||||
 | 
					                                isHidden ? "fa-eye" : "fa-eye-slash"
 | 
				
			||||||
 | 
					                            }`}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
                    </button>
 | 
					                    </button>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                )
 | 
					            ) : null}
 | 
				
			||||||
                : null}
 | 
					 | 
				
			||||||
        </Input>
 | 
					        </Input>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,18 @@
 | 
				
			|||||||
Password Bootstrap component input with label and predefined sizes and structure for using in foris forms.
 | 
					Password Bootstrap component input with label and predefined sizes and structure
 | 
				
			||||||
Can be used with "eye" button, see example.
 | 
					for using in foris forms. Can be used with "eye" button, see example.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
All additional `props` are passed to the `<input type="password">` HTML component.
 | 
					All additional `props` are passed to the `<input type="password">` HTML
 | 
				
			||||||
 | 
					component.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
import {useState} from 'react';
 | 
					import { useState } from "react";
 | 
				
			||||||
const [value, setValue] = useState('secret');
 | 
					const [value, setValue] = useState("secret");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<PasswordInput
 | 
					<PasswordInput
 | 
				
			||||||
    withEye
 | 
					    withEye
 | 
				
			||||||
    value={value}
 | 
					    value={value}
 | 
				
			||||||
    label="Some password"
 | 
					    label="Some password"
 | 
				
			||||||
    helpText="Read the small text!"
 | 
					    helpText="Read the small text!"
 | 
				
			||||||
    onChange={event =>setValue(event.target.value)}
 | 
					    onChange={(event) => setValue(event.target.value)}
 | 
				
			||||||
/>
 | 
					/>;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,8 @@ RadioSet.propTypes = {
 | 
				
			|||||||
    /** RadioSet label . */
 | 
					    /** RadioSet label . */
 | 
				
			||||||
    label: PropTypes.string,
 | 
					    label: PropTypes.string,
 | 
				
			||||||
    /** Choices . */
 | 
					    /** Choices . */
 | 
				
			||||||
    choices: PropTypes.arrayOf(PropTypes.shape({
 | 
					    choices: PropTypes.arrayOf(
 | 
				
			||||||
 | 
					        PropTypes.shape({
 | 
				
			||||||
            /** Choice lable . */
 | 
					            /** Choice lable . */
 | 
				
			||||||
            label: PropTypes.oneOfType([
 | 
					            label: PropTypes.oneOfType([
 | 
				
			||||||
                PropTypes.string,
 | 
					                PropTypes.string,
 | 
				
			||||||
@@ -24,17 +25,17 @@ RadioSet.propTypes = {
 | 
				
			|||||||
                PropTypes.arrayOf(PropTypes.node),
 | 
					                PropTypes.arrayOf(PropTypes.node),
 | 
				
			||||||
            ]).isRequired,
 | 
					            ]).isRequired,
 | 
				
			||||||
            /** Choice value . */
 | 
					            /** Choice value . */
 | 
				
			||||||
        value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
 | 
					            value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
 | 
				
			||||||
    })).isRequired,
 | 
					                .isRequired,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    ).isRequired,
 | 
				
			||||||
    /** Initial value . */
 | 
					    /** Initial value . */
 | 
				
			||||||
    value: PropTypes.string,
 | 
					    value: PropTypes.string,
 | 
				
			||||||
    /** Help text message . */
 | 
					    /** Help text message . */
 | 
				
			||||||
    helpText: PropTypes.string,
 | 
					    helpText: PropTypes.string,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RadioSet({
 | 
					export function RadioSet({ name, label, choices, value, helpText, ...props }) {
 | 
				
			||||||
    name, label, choices, value, helpText, ...props
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
    const uid = useUID();
 | 
					    const uid = useUID();
 | 
				
			||||||
    const radios = choices.map((choice, key) => {
 | 
					    const radios = choices.map((choice, key) => {
 | 
				
			||||||
        const id = `${name}-${key}`;
 | 
					        const id = `${name}-${key}`;
 | 
				
			||||||
@@ -47,7 +48,6 @@ export function RadioSet({
 | 
				
			|||||||
                value={choice.value}
 | 
					                value={choice.value}
 | 
				
			||||||
                helpText={choice.helpText}
 | 
					                helpText={choice.helpText}
 | 
				
			||||||
                checked={choice.value === value}
 | 
					                checked={choice.value === value}
 | 
				
			||||||
 | 
					 | 
				
			||||||
                {...props}
 | 
					                {...props}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
@@ -55,9 +55,15 @@ export function RadioSet({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <div className="form-group">
 | 
					        <div className="form-group">
 | 
				
			||||||
            {label && <label htmlFor={uid} className="d-block">{label}</label>}
 | 
					            {label && (
 | 
				
			||||||
 | 
					                <label htmlFor={uid} className="d-block">
 | 
				
			||||||
 | 
					                    {label}
 | 
				
			||||||
 | 
					                </label>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
            {radios}
 | 
					            {radios}
 | 
				
			||||||
            {helpText && <small className="form-text text-muted">{helpText}</small>}
 | 
					            {helpText && (
 | 
				
			||||||
 | 
					                <small className="form-text text-muted">{helpText}</small>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -73,21 +79,28 @@ Radio.propTypes = {
 | 
				
			|||||||
    helpText: PropTypes.string,
 | 
					    helpText: PropTypes.string,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Radio({
 | 
					export function Radio({ label, id, helpText, ...props }) {
 | 
				
			||||||
    label, id, helpText, ...props
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
            <div className={`custom-control custom-radio ${!helpText ? "custom-control-inline" : ""}`.trim()}>
 | 
					            <div
 | 
				
			||||||
 | 
					                className={`custom-control custom-radio ${
 | 
				
			||||||
 | 
					                    !helpText ? "custom-control-inline" : ""
 | 
				
			||||||
 | 
					                }`.trim()}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
                <input
 | 
					                <input
 | 
				
			||||||
                    id={id}
 | 
					                    id={id}
 | 
				
			||||||
                    className="custom-control-input"
 | 
					                    className="custom-control-input"
 | 
				
			||||||
                    type="radio"
 | 
					                    type="radio"
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    {...props}
 | 
					                    {...props}
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                <label className="custom-control-label" htmlFor={id}>{label}</label>
 | 
					                <label className="custom-control-label" htmlFor={id}>
 | 
				
			||||||
                {helpText && <small className="form-text text-muted mt-0 mb-3">{helpText}</small>}
 | 
					                    {label}
 | 
				
			||||||
 | 
					                </label>
 | 
				
			||||||
 | 
					                {helpText && (
 | 
				
			||||||
 | 
					                    <small className="form-text text-muted mt-0 mb-3">
 | 
				
			||||||
 | 
					                        {helpText}
 | 
				
			||||||
 | 
					                    </small>
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </>
 | 
					        </>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,16 @@
 | 
				
			|||||||
Set of radio Bootstrap component input with label and predefined sizes and structure for using in foris forms.
 | 
					Set of radio Bootstrap component input with label and predefined sizes and
 | 
				
			||||||
 | 
					structure for using in foris forms.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
All additional `props` are passed to the `<input type="number">` HTML component.
 | 
					All additional `props` are passed to the `<input type="number">` HTML component.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Unless `helpText` is set for one of the options they are displayed inline.
 | 
					Unless `helpText` is set for one of the options they are displayed inline.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
import {useState} from 'react';
 | 
					import { useState } from "react";
 | 
				
			||||||
const CHOICES=[
 | 
					const CHOICES = [
 | 
				
			||||||
    {value:'one',label:'1'},
 | 
					    { value: "one", label: "1" },
 | 
				
			||||||
    {value:'two',label:'2'},
 | 
					    { value: "two", label: "2" },
 | 
				
			||||||
    {value:'three',label:'3'},
 | 
					    { value: "three", label: "3" },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
const [value, setValue] = useState(CHOICES[0].value);
 | 
					const [value, setValue] = useState(CHOICES[0].value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -17,10 +18,10 @@ const [value, setValue] = useState(CHOICES[0].value);
 | 
				
			|||||||
    {/*Yeah, it gets event, not value!*/}
 | 
					    {/*Yeah, it gets event, not value!*/}
 | 
				
			||||||
    <RadioSet
 | 
					    <RadioSet
 | 
				
			||||||
        value={value}
 | 
					        value={value}
 | 
				
			||||||
        name='some-radio'
 | 
					        name="some-radio"
 | 
				
			||||||
        choices={CHOICES}
 | 
					        choices={CHOICES}
 | 
				
			||||||
        onChange={event =>setValue(event.target.value)}
 | 
					        onChange={(event) => setValue(event.target.value)}
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
    <p>Selected value: {value}</p>
 | 
					    <p>Selected value: {value}</p>
 | 
				
			||||||
</>
 | 
					</>;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,35 +15,30 @@ Select.propTypes = {
 | 
				
			|||||||
    /** Choices if form of {value : "Label",...}. */
 | 
					    /** Choices if form of {value : "Label",...}. */
 | 
				
			||||||
    choices: PropTypes.object.isRequired,
 | 
					    choices: PropTypes.object.isRequired,
 | 
				
			||||||
    /** Current value. */
 | 
					    /** Current value. */
 | 
				
			||||||
    value: PropTypes.oneOfType([
 | 
					    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
 | 
				
			||||||
        PropTypes.string,
 | 
					 | 
				
			||||||
        PropTypes.number,
 | 
					 | 
				
			||||||
    ]).isRequired,
 | 
					 | 
				
			||||||
    /** Help text message. */
 | 
					    /** Help text message. */
 | 
				
			||||||
    helpText: PropTypes.string,
 | 
					    helpText: PropTypes.string,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Select({
 | 
					export function Select({ label, choices, helpText, ...props }) {
 | 
				
			||||||
    label, choices, helpText, ...props
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
    const uid = useUID();
 | 
					    const uid = useUID();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const options = Object.keys(choices).sort(
 | 
					    const options = Object.keys(choices)
 | 
				
			||||||
        (a, b) => a - b || a.toString().localeCompare(b.toString()),
 | 
					        .sort((a, b) => a - b || a.toString().localeCompare(b.toString()))
 | 
				
			||||||
    ).map(
 | 
					        .map((key) => (
 | 
				
			||||||
        (key) => <option key={key} value={key}>{choices[key]}</option>,
 | 
					            <option key={key} value={key}>
 | 
				
			||||||
    );
 | 
					                {choices[key]}
 | 
				
			||||||
 | 
					            </option>
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <div className="form-group">
 | 
					        <div className="form-group">
 | 
				
			||||||
            <label htmlFor={uid}>{label}</label>
 | 
					            <label htmlFor={uid}>{label}</label>
 | 
				
			||||||
            <select
 | 
					            <select className="custom-select" id={uid} {...props}>
 | 
				
			||||||
                className="custom-select"
 | 
					 | 
				
			||||||
                id={uid}
 | 
					 | 
				
			||||||
                {...props}
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
                {options}
 | 
					                {options}
 | 
				
			||||||
            </select>
 | 
					            </select>
 | 
				
			||||||
            {helpText ? <small className="form-text text-muted">{helpText}</small> : null}
 | 
					            {helpText ? (
 | 
				
			||||||
 | 
					                <small className="form-text text-muted">{helpText}</small>
 | 
				
			||||||
 | 
					            ) : null}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,14 @@
 | 
				
			|||||||
Select with options Bootstrap component input with label and predefined sizes and structure for using in foris forms.
 | 
					Select with options Bootstrap component input with label and predefined sizes
 | 
				
			||||||
 | 
					and structure for using in foris forms.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
All additional `props` are passed to the `<select>` HTML component.
 | 
					All additional `props` are passed to the `<select>` HTML component.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
import {useState} from 'react';
 | 
					import { useState } from "react";
 | 
				
			||||||
const CHOICES={
 | 
					const CHOICES = {
 | 
				
			||||||
    apple:'Apple',
 | 
					    apple: "Apple",
 | 
				
			||||||
    banana:'Banana',
 | 
					    banana: "Banana",
 | 
				
			||||||
    peach:'Peach',
 | 
					    peach: "Peach",
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
const [value, setValue] = useState(Object.keys(CHOICES)[0]);
 | 
					const [value, setValue] = useState(Object.keys(CHOICES)[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -17,9 +18,9 @@ const [value, setValue] = useState(Object.keys(CHOICES)[0]);
 | 
				
			|||||||
        label="Fruit"
 | 
					        label="Fruit"
 | 
				
			||||||
        value={value}
 | 
					        value={value}
 | 
				
			||||||
        choices={CHOICES}
 | 
					        choices={CHOICES}
 | 
				
			||||||
        onChange={event=>setValue(event.target.value)}
 | 
					        onChange={(event) => setValue(event.target.value)}
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
    <p>Selected choice label: {CHOICES[value]}</p>
 | 
					    <p>Selected choice label: {CHOICES[value]}</p>
 | 
				
			||||||
    <p>Selected choice value: {value}</p>
 | 
					    <p>Selected choice value: {value}</p>
 | 
				
			||||||
</>
 | 
					</>;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.spinner-fs-background {
 | 
					.spinner-fs-background {
 | 
				
			||||||
    background-color: rgba(2, 2, 2, .5);
 | 
					    background-color: rgba(2, 2, 2, 0.5);
 | 
				
			||||||
    color: rgb(230, 230, 230);
 | 
					    color: rgb(230, 230, 230);
 | 
				
			||||||
    position: fixed;
 | 
					    position: fixed;
 | 
				
			||||||
    width: 100%;
 | 
					    width: 100%;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,12 +25,12 @@ Spinner.defaultProps = {
 | 
				
			|||||||
    fullScreen: false,
 | 
					    fullScreen: false,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Spinner({
 | 
					export function Spinner({ fullScreen, children, className }) {
 | 
				
			||||||
    fullScreen, children, className,
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
    if (!fullScreen) {
 | 
					    if (!fullScreen) {
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            <div className={`spinner-wrapper ${className || "my-3 text-center"}`}>
 | 
					            <div
 | 
				
			||||||
 | 
					                className={`spinner-wrapper ${className || "my-3 text-center"}`}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
                <SpinnerElement>{children}</SpinnerElement>
 | 
					                <SpinnerElement>{children}</SpinnerElement>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
@@ -61,7 +61,9 @@ export function SpinnerElement({ small, className, children }) {
 | 
				
			|||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
            <div
 | 
					            <div
 | 
				
			||||||
                className={`spinner-border ${small ? "spinner-border-sm" : ""} ${className || ""}`.trim()}
 | 
					                className={`spinner-border ${
 | 
				
			||||||
 | 
					                    small ? "spinner-border-sm" : ""
 | 
				
			||||||
 | 
					                } ${className || ""}`.trim()}
 | 
				
			||||||
                role="status"
 | 
					                role="status"
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
                <span className="sr-only" />
 | 
					                <span className="sr-only" />
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										49
									
								
								src/bootstrap/Switch.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/bootstrap/Switch.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright (c) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This is free software, licensed under the GNU General Public License v3.
 | 
				
			||||||
 | 
					 * See /LICENSE for more information.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import PropTypes from "prop-types";
 | 
				
			||||||
 | 
					import { useUID } from "react-uid";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Switch.propTypes = {
 | 
				
			||||||
 | 
					    label: PropTypes.oneOfType([
 | 
				
			||||||
 | 
					        PropTypes.string,
 | 
				
			||||||
 | 
					        PropTypes.element,
 | 
				
			||||||
 | 
					        PropTypes.node,
 | 
				
			||||||
 | 
					        PropTypes.arrayOf(PropTypes.node),
 | 
				
			||||||
 | 
					    ]).isRequired,
 | 
				
			||||||
 | 
					    helpText: PropTypes.string,
 | 
				
			||||||
 | 
					    switchHeading: PropTypes.bool,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function Switch({ label, helpText, switchHeading, ...props }) {
 | 
				
			||||||
 | 
					    const uid = useUID();
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <div className="form-group">
 | 
				
			||||||
 | 
					            <div
 | 
				
			||||||
 | 
					                className={`custom-control custom-switch ${
 | 
				
			||||||
 | 
					                    !helpText ? "custom-control-inline" : ""
 | 
				
			||||||
 | 
					                } ${switchHeading ? "switch-heading" : ""}`.trim()}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                <input
 | 
				
			||||||
 | 
					                    type="checkbox"
 | 
				
			||||||
 | 
					                    className="custom-control-input"
 | 
				
			||||||
 | 
					                    id={uid}
 | 
				
			||||||
 | 
					                    {...props}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <label className="custom-control-label" htmlFor={uid}>
 | 
				
			||||||
 | 
					                    {label}
 | 
				
			||||||
 | 
					                </label>
 | 
				
			||||||
 | 
					                {helpText && (
 | 
				
			||||||
 | 
					                    <small className="form-text text-muted mt-0 mb-3">
 | 
				
			||||||
 | 
					                        {helpText}
 | 
				
			||||||
 | 
					                    </small>
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,15 +1,16 @@
 | 
				
			|||||||
Text Bootstrap component input with label and predefined sizes and structure for using in foris forms.
 | 
					Text Bootstrap component input with label and predefined sizes and structure for
 | 
				
			||||||
 | 
					using in foris forms.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
All additional `props` are passed to the `<input type="text">` HTML component.
 | 
					All additional `props` are passed to the `<input type="text">` HTML component.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
import {useState} from 'react';
 | 
					import { useState } from "react";
 | 
				
			||||||
const [value, setValue] = useState('Bla bla');
 | 
					const [value, setValue] = useState("Bla bla");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<TextInput
 | 
					<TextInput
 | 
				
			||||||
    value={value}
 | 
					    value={value}
 | 
				
			||||||
    label="Some text"
 | 
					    label="Some text"
 | 
				
			||||||
    helpText="Read the small text!"
 | 
					    helpText="Read the small text!"
 | 
				
			||||||
    onChange={event =>setValue(event.target.value)}
 | 
					    onChange={(event) => setValue(event.target.value)}
 | 
				
			||||||
/>
 | 
					/>;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,19 +14,18 @@ import { Button } from "../Button";
 | 
				
			|||||||
describe("<Button />", () => {
 | 
					describe("<Button />", () => {
 | 
				
			||||||
    it("Render button correctly", () => {
 | 
					    it("Render button correctly", () => {
 | 
				
			||||||
        const { container } = render(<Button>Test Button</Button>);
 | 
					        const { container } = render(<Button>Test Button</Button>);
 | 
				
			||||||
        expect(container.firstChild)
 | 
					        expect(container.firstChild).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Render button with custom classes", () => {
 | 
					    it("Render button with custom classes", () => {
 | 
				
			||||||
        const { container } = render(<Button className="one two three">Test Button</Button>);
 | 
					        const { container } = render(
 | 
				
			||||||
        expect(container.firstChild)
 | 
					            <Button className="one two three">Test Button</Button>
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					        );
 | 
				
			||||||
 | 
					        expect(container.firstChild).toMatchSnapshot();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Render button with spinner", () => {
 | 
					    it("Render button with spinner", () => {
 | 
				
			||||||
        const { container } = render(<Button loading>Test Button</Button>);
 | 
					        const { container } = render(<Button loading>Test Button</Button>);
 | 
				
			||||||
        expect(container.firstChild)
 | 
					        expect(container.firstChild).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,22 +18,16 @@ describe("<Checkbox/>", () => {
 | 
				
			|||||||
                label="Test label"
 | 
					                label="Test label"
 | 
				
			||||||
                checked
 | 
					                checked
 | 
				
			||||||
                helpText="Some help text"
 | 
					                helpText="Some help text"
 | 
				
			||||||
                onChange={() => {
 | 
					                onChange={() => {}}
 | 
				
			||||||
                }}
 | 
					            />
 | 
				
			||||||
            />,
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        expect(container.firstChild)
 | 
					        expect(container.firstChild).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Render uncheked checkbox", () => {
 | 
					    it("Render uncheked checkbox", () => {
 | 
				
			||||||
        const { container } = render(
 | 
					        const { container } = render(
 | 
				
			||||||
            <CheckBox
 | 
					            <CheckBox label="Test label" helpText="Some help text" />
 | 
				
			||||||
                label="Test label"
 | 
					 | 
				
			||||||
                helpText="Some help text"
 | 
					 | 
				
			||||||
            />,
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        expect(container.firstChild)
 | 
					        expect(container.firstChild).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,11 @@ import { DownloadButton } from "../DownloadButton";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
describe("<DownloadButton />", () => {
 | 
					describe("<DownloadButton />", () => {
 | 
				
			||||||
    it("should have download attribute", () => {
 | 
					    it("should have download attribute", () => {
 | 
				
			||||||
        const { container } = render(<DownloadButton href="http://example.com">Test Button</DownloadButton>);
 | 
					        const { container } = render(
 | 
				
			||||||
 | 
					            <DownloadButton href="http://example.com">
 | 
				
			||||||
 | 
					                Test Button
 | 
				
			||||||
 | 
					            </DownloadButton>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        expect(container.firstChild.getAttribute("download")).not.toBeNull();
 | 
					        expect(container.firstChild.getAttribute("download")).not.toBeNull();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,9 +7,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import { render, fireEvent, getByLabelText, wait } from "customTestRender";
 | 
				
			||||||
    render, fireEvent, getByLabelText, wait,
 | 
					 | 
				
			||||||
} from "customTestRender";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { NumberInput } from "../NumberInput";
 | 
					import { NumberInput } from "../NumberInput";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -24,7 +22,7 @@ describe("<NumberInput/>", () => {
 | 
				
			|||||||
                helpText="Some help text"
 | 
					                helpText="Some help text"
 | 
				
			||||||
                value={1}
 | 
					                value={1}
 | 
				
			||||||
                onChange={onChangeMock}
 | 
					                onChange={onChangeMock}
 | 
				
			||||||
            />,
 | 
					            />
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        componentContainer = container;
 | 
					        componentContainer = container;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -36,12 +34,16 @@ describe("<NumberInput/>", () => {
 | 
				
			|||||||
    it("Increase number with button", async () => {
 | 
					    it("Increase number with button", async () => {
 | 
				
			||||||
        const increaseButton = getByLabelText(componentContainer, "Increase");
 | 
					        const increaseButton = getByLabelText(componentContainer, "Increase");
 | 
				
			||||||
        fireEvent.mouseDown(increaseButton);
 | 
					        fireEvent.mouseDown(increaseButton);
 | 
				
			||||||
        await wait(() => expect(onChangeMock).toHaveBeenCalledWith({ target: { value: 2 } }));
 | 
					        await wait(() =>
 | 
				
			||||||
 | 
					            expect(onChangeMock).toHaveBeenCalledWith({ target: { value: 2 } })
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Decrease number with button", async () => {
 | 
					    it("Decrease number with button", async () => {
 | 
				
			||||||
        const decreaseButton = getByLabelText(componentContainer, "Decrease");
 | 
					        const decreaseButton = getByLabelText(componentContainer, "Decrease");
 | 
				
			||||||
        fireEvent.mouseDown(decreaseButton);
 | 
					        fireEvent.mouseDown(decreaseButton);
 | 
				
			||||||
        await wait(() => expect(onChangeMock).toHaveBeenCalledWith({ target: { value: 0 } }));
 | 
					        await wait(() =>
 | 
				
			||||||
 | 
					            expect(onChangeMock).toHaveBeenCalledWith({ target: { value: 0 } })
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,11 +18,9 @@ describe("<PasswordInput/>", () => {
 | 
				
			|||||||
                label="Test label"
 | 
					                label="Test label"
 | 
				
			||||||
                helpText="Some help text"
 | 
					                helpText="Some help text"
 | 
				
			||||||
                value="Some password"
 | 
					                value="Some password"
 | 
				
			||||||
                onChange={() => {
 | 
					                onChange={() => {}}
 | 
				
			||||||
                }}
 | 
					            />
 | 
				
			||||||
            />,
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        expect(container.firstChild)
 | 
					        expect(container.firstChild).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,11 +35,9 @@ describe("<RadioSet/>", () => {
 | 
				
			|||||||
                value="value"
 | 
					                value="value"
 | 
				
			||||||
                choices={TEST_CHOICES}
 | 
					                choices={TEST_CHOICES}
 | 
				
			||||||
                helpText="Some help text"
 | 
					                helpText="Some help text"
 | 
				
			||||||
                onChange={() => {
 | 
					                onChange={() => {}}
 | 
				
			||||||
                }}
 | 
					            />
 | 
				
			||||||
            />,
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        expect(container.firstChild)
 | 
					        expect(container.firstChild).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,10 @@
 | 
				
			|||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    fireEvent, getByDisplayValue, getByText, render,
 | 
					    fireEvent,
 | 
				
			||||||
 | 
					    getByDisplayValue,
 | 
				
			||||||
 | 
					    getByText,
 | 
				
			||||||
 | 
					    render,
 | 
				
			||||||
} from "customTestRender";
 | 
					} from "customTestRender";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Select } from "../Select";
 | 
					import { Select } from "../Select";
 | 
				
			||||||
@@ -29,29 +32,24 @@ describe("<Select/>", () => {
 | 
				
			|||||||
                value="1"
 | 
					                value="1"
 | 
				
			||||||
                choices={TEST_CHOICES}
 | 
					                choices={TEST_CHOICES}
 | 
				
			||||||
                helpText="Help text"
 | 
					                helpText="Help text"
 | 
				
			||||||
 | 
					 | 
				
			||||||
                onChange={onChangeHandler}
 | 
					                onChange={onChangeHandler}
 | 
				
			||||||
            />,
 | 
					            />
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        selectContainer = container;
 | 
					        selectContainer = container;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Test with snapshot.", () => {
 | 
					    it("Test with snapshot.", () => {
 | 
				
			||||||
        expect(selectContainer)
 | 
					        expect(selectContainer).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Test onChange handling.", () => {
 | 
					    it("Test onChange handling.", () => {
 | 
				
			||||||
        const select = getByDisplayValue(selectContainer, "one");
 | 
					        const select = getByDisplayValue(selectContainer, "one");
 | 
				
			||||||
        expect(select.value)
 | 
					        expect(select.value).toBe("1");
 | 
				
			||||||
            .toBe("1");
 | 
					 | 
				
			||||||
        fireEvent.change(select, { target: { value: "2" } });
 | 
					        fireEvent.change(select, { target: { value: "2" } });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const option = getByText(selectContainer, "two");
 | 
					        const option = getByText(selectContainer, "two");
 | 
				
			||||||
        expect(onChangeHandler)
 | 
					        expect(onChangeHandler).toBeCalled();
 | 
				
			||||||
            .toBeCalled();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(option.value)
 | 
					        expect(option.value).toBe("2");
 | 
				
			||||||
            .toBe("2");
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										33
									
								
								src/bootstrap/__tests__/Switch.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/bootstrap/__tests__/Switch.test.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This is free software, licensed under the GNU General Public License v3.
 | 
				
			||||||
 | 
					 * See /LICENSE for more information.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { render } from "customTestRender";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Switch } from "../Switch";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("<Switch/>", () => {
 | 
				
			||||||
 | 
					    it("Render switch", () => {
 | 
				
			||||||
 | 
					        const { container } = render(
 | 
				
			||||||
 | 
					            <Switch
 | 
				
			||||||
 | 
					                label="Test label"
 | 
				
			||||||
 | 
					                checked
 | 
				
			||||||
 | 
					                helpText="Some help text"
 | 
				
			||||||
 | 
					                onChange={() => {}}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        expect(container.firstChild).toMatchSnapshot();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("Render uncheked switch", () => {
 | 
				
			||||||
 | 
					        const { container } = render(
 | 
				
			||||||
 | 
					            <Switch label="Test label" helpText="Some help text" />
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        expect(container.firstChild).toMatchSnapshot();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -18,11 +18,9 @@ describe("<TextInput/>", () => {
 | 
				
			|||||||
                label="Test label"
 | 
					                label="Test label"
 | 
				
			||||||
                helpText="Some help text"
 | 
					                helpText="Some help text"
 | 
				
			||||||
                value="Some text"
 | 
					                value="Some text"
 | 
				
			||||||
                onChange={() => {
 | 
					                onChange={() => {}}
 | 
				
			||||||
                }}
 | 
					            />
 | 
				
			||||||
            />,
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        expect(container.firstChild)
 | 
					        expect(container.firstChild).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,8 +5,6 @@ exports[`<Button /> Render button correctly 1`] = `
 | 
				
			|||||||
  class="btn btn-primary "
 | 
					  class="btn btn-primary "
 | 
				
			||||||
  type="button"
 | 
					  type="button"
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
   
 | 
					 | 
				
			||||||
   
 | 
					 | 
				
			||||||
  Test Button
 | 
					  Test Button
 | 
				
			||||||
</button>
 | 
					</button>
 | 
				
			||||||
`;
 | 
					`;
 | 
				
			||||||
@@ -16,8 +14,6 @@ exports[`<Button /> Render button with custom classes 1`] = `
 | 
				
			|||||||
  class="btn one two three"
 | 
					  class="btn one two three"
 | 
				
			||||||
  type="button"
 | 
					  type="button"
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
   
 | 
					 | 
				
			||||||
   
 | 
					 | 
				
			||||||
  Test Button
 | 
					  Test Button
 | 
				
			||||||
</button>
 | 
					</button>
 | 
				
			||||||
`;
 | 
					`;
 | 
				
			||||||
@@ -33,8 +29,6 @@ exports[`<Button /> Render button with spinner 1`] = `
 | 
				
			|||||||
    role="status"
 | 
					    role="status"
 | 
				
			||||||
  />
 | 
					  />
 | 
				
			||||||
   
 | 
					   
 | 
				
			||||||
   
 | 
					 | 
				
			||||||
   
 | 
					 | 
				
			||||||
  Test Button
 | 
					  Test Button
 | 
				
			||||||
</button>
 | 
					</button>
 | 
				
			||||||
`;
 | 
					`;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										56
									
								
								src/bootstrap/__tests__/__snapshots__/Switch.test.js.snap
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/bootstrap/__tests__/__snapshots__/Switch.test.js.snap
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					// Jest Snapshot v1, https://goo.gl/fbAQLP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports[`<Switch/> Render switch 1`] = `
 | 
				
			||||||
 | 
					<div
 | 
				
			||||||
 | 
					  class="form-group"
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
 | 
					  <div
 | 
				
			||||||
 | 
					    class="custom-control custom-switch"
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <input
 | 
				
			||||||
 | 
					      checked=""
 | 
				
			||||||
 | 
					      class="custom-control-input"
 | 
				
			||||||
 | 
					      id="1"
 | 
				
			||||||
 | 
					      type="checkbox"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					    <label
 | 
				
			||||||
 | 
					      class="custom-control-label"
 | 
				
			||||||
 | 
					      for="1"
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      Test label
 | 
				
			||||||
 | 
					    </label>
 | 
				
			||||||
 | 
					    <small
 | 
				
			||||||
 | 
					      class="form-text text-muted mt-0 mb-3"
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      Some help text
 | 
				
			||||||
 | 
					    </small>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports[`<Switch/> Render uncheked switch 1`] = `
 | 
				
			||||||
 | 
					<div
 | 
				
			||||||
 | 
					  class="form-group"
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
 | 
					  <div
 | 
				
			||||||
 | 
					    class="custom-control custom-switch"
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <input
 | 
				
			||||||
 | 
					      class="custom-control-input"
 | 
				
			||||||
 | 
					      id="1"
 | 
				
			||||||
 | 
					      type="checkbox"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					    <label
 | 
				
			||||||
 | 
					      class="custom-control-label"
 | 
				
			||||||
 | 
					      for="1"
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      Test label
 | 
				
			||||||
 | 
					    </label>
 | 
				
			||||||
 | 
					    <small
 | 
				
			||||||
 | 
					      class="form-text text-muted mt-0 mb-3"
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      Some help text
 | 
				
			||||||
 | 
					    </small>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
@@ -7,4 +7,5 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/** Bootstrap column size for form fields */
 | 
					/** Bootstrap column size for form fields */
 | 
				
			||||||
// eslint-disable-next-line import/prefer-default-export
 | 
					// eslint-disable-next-line import/prefer-default-export
 | 
				
			||||||
export const formFieldsSize = "col-sm-12 offset-lg-1 col-lg-10 p-0 mb-3";
 | 
					export const formFieldsSize = "card p-4 col-sm-12 col-lg-12 p-0 mb-3";
 | 
				
			||||||
 | 
					export const buttonFormFieldsSize = "col-sm-12 col-lg-12 p-0 mb-3";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,15 +13,9 @@ import { API_STATE } from "../api/utils";
 | 
				
			|||||||
import { ForisURLs } from "../utils/forisUrls";
 | 
					import { ForisURLs } from "../utils/forisUrls";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Button } from "../bootstrap/Button";
 | 
					import { Button } from "../bootstrap/Button";
 | 
				
			||||||
import {
 | 
					import { Modal, ModalHeader, ModalBody, ModalFooter } from "../bootstrap/Modal";
 | 
				
			||||||
    Modal, ModalHeader, ModalBody, ModalFooter,
 | 
					 | 
				
			||||||
} from "../bootstrap/Modal";
 | 
					 | 
				
			||||||
import { useAlert } from "../alertContext/AlertContext";
 | 
					import { useAlert } from "../alertContext/AlertContext";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RebootButton.propTypes = {
 | 
					 | 
				
			||||||
    forisFormSize: PropTypes.bool,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function RebootButton(props) {
 | 
					export function RebootButton(props) {
 | 
				
			||||||
    const [triggered, setTriggered] = useState(false);
 | 
					    const [triggered, setTriggered] = useState(false);
 | 
				
			||||||
    const [modalShown, setModalShown] = useState(false);
 | 
					    const [modalShown, setModalShown] = useState(false);
 | 
				
			||||||
@@ -42,13 +36,16 @@ export function RebootButton(props) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
            <RebootModal shown={modalShown} setShown={setModalShown} onReboot={rebootHandler} />
 | 
					            <RebootModal
 | 
				
			||||||
 | 
					                shown={modalShown}
 | 
				
			||||||
 | 
					                setShown={setModalShown}
 | 
				
			||||||
 | 
					                onReboot={rebootHandler}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
            <Button
 | 
					            <Button
 | 
				
			||||||
                className="btn-danger"
 | 
					                className="btn-danger"
 | 
				
			||||||
                loading={triggered}
 | 
					                loading={triggered}
 | 
				
			||||||
                disabled={triggered}
 | 
					                disabled={triggered}
 | 
				
			||||||
                onClick={() => setModalShown(true)}
 | 
					                onClick={() => setModalShown(true)}
 | 
				
			||||||
 | 
					 | 
				
			||||||
                {...props}
 | 
					                {...props}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
                {_("Reboot")}
 | 
					                {_("Reboot")}
 | 
				
			||||||
@@ -67,10 +64,14 @@ function RebootModal({ shown, setShown, onReboot }) {
 | 
				
			|||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Modal shown={shown} setShown={setShown}>
 | 
					        <Modal shown={shown} setShown={setShown}>
 | 
				
			||||||
            <ModalHeader setShown={setShown} title={_("Reboot confirmation")} />
 | 
					            <ModalHeader setShown={setShown} title={_("Reboot confirmation")} />
 | 
				
			||||||
            <ModalBody><p>{_("Are you sure you want to restart the router?")}</p></ModalBody>
 | 
					            <ModalBody>
 | 
				
			||||||
 | 
					                <p>{_("Are you sure you want to restart the router?")}</p>
 | 
				
			||||||
 | 
					            </ModalBody>
 | 
				
			||||||
            <ModalFooter>
 | 
					            <ModalFooter>
 | 
				
			||||||
                <Button onClick={() => setShown(false)}>{_("Cancel")}</Button>
 | 
					                <Button onClick={() => setShown(false)}>{_("Cancel")}</Button>
 | 
				
			||||||
                <Button className="btn-danger" onClick={onReboot}>{_("Confirm reboot")}</Button>
 | 
					                <Button className="btn-danger" onClick={onReboot}>
 | 
				
			||||||
 | 
					                    {_("Confirm reboot")}
 | 
				
			||||||
 | 
					                </Button>
 | 
				
			||||||
            </ModalFooter>
 | 
					            </ModalFooter>
 | 
				
			||||||
        </Modal>
 | 
					        </Modal>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@ import { useAlert } from "../../alertContext/AlertContext";
 | 
				
			|||||||
import { ALERT_TYPES } from "../../bootstrap/Alert";
 | 
					import { ALERT_TYPES } from "../../bootstrap/Alert";
 | 
				
			||||||
import { useAPIPost } from "../../api/hooks";
 | 
					import { useAPIPost } from "../../api/hooks";
 | 
				
			||||||
import { API_STATE } from "../../api/utils";
 | 
					import { API_STATE } from "../../api/utils";
 | 
				
			||||||
import { formFieldsSize } from "../../bootstrap/constants";
 | 
					import { buttonFormFieldsSize } from "../../bootstrap/constants";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ResetWiFiSettings.propTypes = {
 | 
					ResetWiFiSettings.propTypes = {
 | 
				
			||||||
    ws: PropTypes.object.isRequired,
 | 
					    ws: PropTypes.object.isRequired,
 | 
				
			||||||
@@ -25,8 +25,7 @@ export default function ResetWiFiSettings({ ws, endpoint }) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
        const module = "wifi";
 | 
					        const module = "wifi";
 | 
				
			||||||
        ws.subscribe(module)
 | 
					        ws.subscribe(module).bind(module, "reset", () => {
 | 
				
			||||||
            .bind(module, "reset", () => {
 | 
					 | 
				
			||||||
            // eslint-disable-next-line no-restricted-globals
 | 
					            // eslint-disable-next-line no-restricted-globals
 | 
				
			||||||
            setTimeout(() => location.reload(), 1000);
 | 
					            setTimeout(() => location.reload(), 1000);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
@@ -38,7 +37,10 @@ export default function ResetWiFiSettings({ ws, endpoint }) {
 | 
				
			|||||||
        if (postResetResponse.state === API_STATE.ERROR) {
 | 
					        if (postResetResponse.state === API_STATE.ERROR) {
 | 
				
			||||||
            setAlert(_("An error occurred during resetting Wi-Fi settings."));
 | 
					            setAlert(_("An error occurred during resetting Wi-Fi settings."));
 | 
				
			||||||
        } else if (postResetResponse.state === API_STATE.SUCCESS) {
 | 
					        } else if (postResetResponse.state === API_STATE.SUCCESS) {
 | 
				
			||||||
            setAlert(_("Wi-Fi settings are set to defaults."), ALERT_TYPES.SUCCESS);
 | 
					            setAlert(
 | 
				
			||||||
 | 
					                _("Wi-Fi settings are set to defaults."),
 | 
				
			||||||
 | 
					                ALERT_TYPES.SUCCESS
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }, [postResetResponse, setAlert]);
 | 
					    }, [postResetResponse, setAlert]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -50,20 +52,19 @@ export default function ResetWiFiSettings({ ws, endpoint }) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
            <h4>{_("Reset Wi-Fi Settings")}</h4>
 | 
					            <h2>{_("Reset Wi-Fi Settings")}</h2>
 | 
				
			||||||
            <p>
 | 
					            <p>
 | 
				
			||||||
                {_(`
 | 
					                {_(`
 | 
				
			||||||
If a number of wireless cards doesn't match, you may try to reset the Wi-Fi settings. Note that this will remove the
 | 
					If a number of wireless cards doesn't match, you may try to reset the Wi-Fi settings. Note that this will remove the
 | 
				
			||||||
current Wi-Fi configuration and restore the default values.
 | 
					current Wi-Fi configuration and restore the default values.
 | 
				
			||||||
        `)}
 | 
					        `)}
 | 
				
			||||||
            </p>
 | 
					            </p>
 | 
				
			||||||
            <div className={`${formFieldsSize} text-right`}>
 | 
					            <div className={`${buttonFormFieldsSize} text-right`}>
 | 
				
			||||||
                <Button
 | 
					                <Button
 | 
				
			||||||
                    className="btn-warning"
 | 
					                    className="btn-warning"
 | 
				
			||||||
                    forisFormSize
 | 
					                    forisFormSize
 | 
				
			||||||
                    loading={isLoading}
 | 
					                    loading={isLoading}
 | 
				
			||||||
                    disabled={isLoading}
 | 
					                    disabled={isLoading}
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    onClick={onReset}
 | 
					                    onClick={onReset}
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                    {_("Reset Wi-Fi Settings")}
 | 
					                    {_("Reset Wi-Fi Settings")}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import PropTypes from "prop-types";
 | 
					import PropTypes from "prop-types";
 | 
				
			||||||
 | 
					import { Switch } from "../../bootstrap/Switch";
 | 
				
			||||||
import { CheckBox } from "../../bootstrap/CheckBox";
 | 
					import { CheckBox } from "../../bootstrap/CheckBox";
 | 
				
			||||||
import { PasswordInput } from "../../bootstrap/PasswordInput";
 | 
					import { PasswordInput } from "../../bootstrap/PasswordInput";
 | 
				
			||||||
import { RadioSet } from "../../bootstrap/RadioSet";
 | 
					import { RadioSet } from "../../bootstrap/RadioSet";
 | 
				
			||||||
@@ -18,25 +18,25 @@ import WifiGuestForm from "./WiFiGuestForm";
 | 
				
			|||||||
import { HELP_TEXTS, HTMODES, HWMODES } from "./constants";
 | 
					import { HELP_TEXTS, HTMODES, HWMODES } from "./constants";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WiFiForm.propTypes = {
 | 
					WiFiForm.propTypes = {
 | 
				
			||||||
    formData: PropTypes.shape(
 | 
					    formData: PropTypes.shape({ devices: PropTypes.arrayOf(PropTypes.object) })
 | 
				
			||||||
        { devices: PropTypes.arrayOf(PropTypes.object) },
 | 
					        .isRequired,
 | 
				
			||||||
    ).isRequired,
 | 
					    formErrors: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
 | 
				
			||||||
    formErrors: PropTypes.oneOfType([
 | 
					 | 
				
			||||||
        PropTypes.object,
 | 
					 | 
				
			||||||
        PropTypes.array,
 | 
					 | 
				
			||||||
    ]),
 | 
					 | 
				
			||||||
    setFormValue: PropTypes.func.isRequired,
 | 
					    setFormValue: PropTypes.func.isRequired,
 | 
				
			||||||
    hasGuestNetwork: PropTypes.bool,
 | 
					    hasGuestNetwork: PropTypes.bool,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WiFiForm.defaultProps = {
 | 
					WiFiForm.defaultProps = {
 | 
				
			||||||
    formData: { devices: [] },
 | 
					    formData: { devices: [] },
 | 
				
			||||||
    setFormValue: () => { },
 | 
					    setFormValue: () => {},
 | 
				
			||||||
    hasGuestNetwork: true,
 | 
					    hasGuestNetwork: true,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function WiFiForm({
 | 
					export default function WiFiForm({
 | 
				
			||||||
    formData, formErrors, setFormValue, hasGuestNetwork, disabled,
 | 
					    formData,
 | 
				
			||||||
 | 
					    formErrors,
 | 
				
			||||||
 | 
					    setFormValue,
 | 
				
			||||||
 | 
					    hasGuestNetwork,
 | 
				
			||||||
 | 
					    disabled,
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
    return formData.devices.map((device, index) => (
 | 
					    return formData.devices.map((device, index) => (
 | 
				
			||||||
        <DeviceForm
 | 
					        <DeviceForm
 | 
				
			||||||
@@ -47,6 +47,7 @@ export default function WiFiForm({
 | 
				
			|||||||
            setFormValue={setFormValue}
 | 
					            setFormValue={setFormValue}
 | 
				
			||||||
            hasGuestNetwork={hasGuestNetwork}
 | 
					            hasGuestNetwork={hasGuestNetwork}
 | 
				
			||||||
            disabled={disabled}
 | 
					            disabled={disabled}
 | 
				
			||||||
 | 
					            divider={index + 1 !== formData.devices.length}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
    ));
 | 
					    ));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -67,6 +68,7 @@ DeviceForm.propTypes = {
 | 
				
			|||||||
    setFormValue: PropTypes.func.isRequired,
 | 
					    setFormValue: PropTypes.func.isRequired,
 | 
				
			||||||
    hasGuestNetwork: PropTypes.bool,
 | 
					    hasGuestNetwork: PropTypes.bool,
 | 
				
			||||||
    deviceIndex: PropTypes.number,
 | 
					    deviceIndex: PropTypes.number,
 | 
				
			||||||
 | 
					    divider: PropTypes.bool,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DeviceForm.defaultProps = {
 | 
					DeviceForm.defaultProps = {
 | 
				
			||||||
@@ -75,40 +77,42 @@ DeviceForm.defaultProps = {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function DeviceForm({
 | 
					function DeviceForm({
 | 
				
			||||||
    formData, formErrors, setFormValue, hasGuestNetwork, deviceIndex, ...props
 | 
					    formData,
 | 
				
			||||||
 | 
					    formErrors,
 | 
				
			||||||
 | 
					    setFormValue,
 | 
				
			||||||
 | 
					    hasGuestNetwork,
 | 
				
			||||||
 | 
					    deviceIndex,
 | 
				
			||||||
 | 
					    divider,
 | 
				
			||||||
 | 
					    ...props
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
    const deviceID = formData.id;
 | 
					    const deviceID = formData.id;
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
            <h3>{_(`Wi-Fi ${deviceID + 1}`)}</h3>
 | 
					            <Switch
 | 
				
			||||||
            <CheckBox
 | 
					                label={<h2>{_(`Wi-Fi ${deviceID + 1}`)}</h2>}
 | 
				
			||||||
                label={_("Enable")}
 | 
					 | 
				
			||||||
                checked={formData.enabled}
 | 
					                checked={formData.enabled}
 | 
				
			||||||
 | 
					                onChange={setFormValue((value) => ({
 | 
				
			||||||
                onChange={setFormValue(
 | 
					                    devices: {
 | 
				
			||||||
                    (value) => ({ devices: { [deviceIndex]: { enabled: { $set: value } } } }),
 | 
					                        [deviceIndex]: { enabled: { $set: value } },
 | 
				
			||||||
                )}
 | 
					                    },
 | 
				
			||||||
 | 
					                }))}
 | 
				
			||||||
 | 
					                switchHeading
 | 
				
			||||||
                {...props}
 | 
					                {...props}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
            {formData.enabled
 | 
					            {formData.enabled ? (
 | 
				
			||||||
                ? (
 | 
					 | 
				
			||||||
                <>
 | 
					                <>
 | 
				
			||||||
                    <TextInput
 | 
					                    <TextInput
 | 
				
			||||||
                        label="SSID"
 | 
					                        label="SSID"
 | 
				
			||||||
                        value={formData.SSID}
 | 
					                        value={formData.SSID}
 | 
				
			||||||
                        error={formErrors.SSID || null}
 | 
					                        error={formErrors.SSID || null}
 | 
				
			||||||
                        required
 | 
					                        required
 | 
				
			||||||
                            onChange={setFormValue(
 | 
					                        onChange={setFormValue((value) => ({
 | 
				
			||||||
                                (value) => ({
 | 
					 | 
				
			||||||
                            devices: {
 | 
					                            devices: {
 | 
				
			||||||
                                [deviceIndex]: {
 | 
					                                [deviceIndex]: {
 | 
				
			||||||
                                    SSID: { $set: value },
 | 
					                                    SSID: { $set: value },
 | 
				
			||||||
                                },
 | 
					                                },
 | 
				
			||||||
                            },
 | 
					                            },
 | 
				
			||||||
                                }),
 | 
					                        }))}
 | 
				
			||||||
                            )}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        {...props}
 | 
					                        {...props}
 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
                        <div className="input-group-append">
 | 
					                        <div className="input-group-append">
 | 
				
			||||||
@@ -126,13 +130,11 @@ function DeviceForm({
 | 
				
			|||||||
                        error={formErrors.password}
 | 
					                        error={formErrors.password}
 | 
				
			||||||
                        helpText={HELP_TEXTS.password}
 | 
					                        helpText={HELP_TEXTS.password}
 | 
				
			||||||
                        required
 | 
					                        required
 | 
				
			||||||
 | 
					                        onChange={setFormValue((value) => ({
 | 
				
			||||||
                            onChange={setFormValue(
 | 
					                            devices: {
 | 
				
			||||||
                                (value) => (
 | 
					                                [deviceIndex]: { password: { $set: value } },
 | 
				
			||||||
                                    { devices: { [deviceIndex]: { password: { $set: value } } } }
 | 
					                            },
 | 
				
			||||||
                                ),
 | 
					                        }))}
 | 
				
			||||||
                            )}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        {...props}
 | 
					                        {...props}
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -140,13 +142,11 @@ function DeviceForm({
 | 
				
			|||||||
                        label="Hide SSID"
 | 
					                        label="Hide SSID"
 | 
				
			||||||
                        helpText={HELP_TEXTS.hidden}
 | 
					                        helpText={HELP_TEXTS.hidden}
 | 
				
			||||||
                        checked={formData.hidden}
 | 
					                        checked={formData.hidden}
 | 
				
			||||||
 | 
					                        onChange={setFormValue((value) => ({
 | 
				
			||||||
                            onChange={setFormValue(
 | 
					                            devices: {
 | 
				
			||||||
                                (value) => (
 | 
					                                [deviceIndex]: { hidden: { $set: value } },
 | 
				
			||||||
                                    { devices: { [deviceIndex]: { hidden: { $set: value } } } }
 | 
					                            },
 | 
				
			||||||
                                ),
 | 
					                        }))}
 | 
				
			||||||
                            )}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        {...props}
 | 
					                        {...props}
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -156,18 +156,14 @@ function DeviceForm({
 | 
				
			|||||||
                        choices={getHwmodeChoices(formData)}
 | 
					                        choices={getHwmodeChoices(formData)}
 | 
				
			||||||
                        value={formData.hwmode}
 | 
					                        value={formData.hwmode}
 | 
				
			||||||
                        helpText={HELP_TEXTS.hwmode}
 | 
					                        helpText={HELP_TEXTS.hwmode}
 | 
				
			||||||
 | 
					                        onChange={setFormValue((value) => ({
 | 
				
			||||||
                            onChange={setFormValue(
 | 
					 | 
				
			||||||
                                (value) => ({
 | 
					 | 
				
			||||||
                            devices: {
 | 
					                            devices: {
 | 
				
			||||||
                                [deviceIndex]: {
 | 
					                                [deviceIndex]: {
 | 
				
			||||||
                                    hwmode: { $set: value },
 | 
					                                    hwmode: { $set: value },
 | 
				
			||||||
                                    channel: { $set: "0" },
 | 
					                                    channel: { $set: "0" },
 | 
				
			||||||
                                },
 | 
					                                },
 | 
				
			||||||
                            },
 | 
					                            },
 | 
				
			||||||
                                }),
 | 
					                        }))}
 | 
				
			||||||
                            )}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        {...props}
 | 
					                        {...props}
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -176,13 +172,11 @@ function DeviceForm({
 | 
				
			|||||||
                        choices={getHtmodeChoices(formData)}
 | 
					                        choices={getHtmodeChoices(formData)}
 | 
				
			||||||
                        value={formData.htmode}
 | 
					                        value={formData.htmode}
 | 
				
			||||||
                        helpText={HELP_TEXTS.htmode}
 | 
					                        helpText={HELP_TEXTS.htmode}
 | 
				
			||||||
 | 
					                        onChange={setFormValue((value) => ({
 | 
				
			||||||
                            onChange={setFormValue(
 | 
					                            devices: {
 | 
				
			||||||
                                (value) => (
 | 
					                                [deviceIndex]: { htmode: { $set: value } },
 | 
				
			||||||
                                    { devices: { [deviceIndex]: { htmode: { $set: value } } } }
 | 
					                            },
 | 
				
			||||||
                                ),
 | 
					                        }))}
 | 
				
			||||||
                            )}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        {...props}
 | 
					                        {...props}
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -190,29 +184,28 @@ function DeviceForm({
 | 
				
			|||||||
                        label="Channel"
 | 
					                        label="Channel"
 | 
				
			||||||
                        choices={getChannelChoices(formData)}
 | 
					                        choices={getChannelChoices(formData)}
 | 
				
			||||||
                        value={formData.channel}
 | 
					                        value={formData.channel}
 | 
				
			||||||
 | 
					                        onChange={setFormValue((value) => ({
 | 
				
			||||||
                            onChange={setFormValue(
 | 
					                            devices: {
 | 
				
			||||||
                                (value) => (
 | 
					                                [deviceIndex]: { channel: { $set: value } },
 | 
				
			||||||
                                    { devices: { [deviceIndex]: { channel: { $set: value } } } }
 | 
					                            },
 | 
				
			||||||
                                ),
 | 
					                        }))}
 | 
				
			||||||
                            )}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        {...props}
 | 
					                        {...props}
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    {hasGuestNetwork && (
 | 
					                    {hasGuestNetwork && (
 | 
				
			||||||
                        <WifiGuestForm
 | 
					                        <WifiGuestForm
 | 
				
			||||||
                                formData={{ id: deviceIndex, ...formData.guest_wifi }}
 | 
					                            formData={{
 | 
				
			||||||
 | 
					                                id: deviceIndex,
 | 
				
			||||||
 | 
					                                ...formData.guest_wifi,
 | 
				
			||||||
 | 
					                            }}
 | 
				
			||||||
                            formErrors={formErrors.guest_wifi || {}}
 | 
					                            formErrors={formErrors.guest_wifi || {}}
 | 
				
			||||||
 | 
					 | 
				
			||||||
                            setFormValue={setFormValue}
 | 
					                            setFormValue={setFormValue}
 | 
				
			||||||
 | 
					 | 
				
			||||||
                            {...props}
 | 
					                            {...props}
 | 
				
			||||||
                        />
 | 
					                        />
 | 
				
			||||||
                    )}
 | 
					                    )}
 | 
				
			||||||
                </>
 | 
					                </>
 | 
				
			||||||
                )
 | 
					            ) : null}
 | 
				
			||||||
                : null}
 | 
					            {divider ? <hr /> : null}
 | 
				
			||||||
        </>
 | 
					        </>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -228,7 +221,9 @@ function getChannelChoices(device) {
 | 
				
			|||||||
        availableBand.available_channels.forEach((availableChannel) => {
 | 
					        availableBand.available_channels.forEach((availableChannel) => {
 | 
				
			||||||
            channelChoices[availableChannel.number.toString()] = `
 | 
					            channelChoices[availableChannel.number.toString()] = `
 | 
				
			||||||
                        ${availableChannel.number}
 | 
					                        ${availableChannel.number}
 | 
				
			||||||
                        (${availableChannel.frequency} MHz ${availableChannel.radar ? " ,DFS" : ""})
 | 
					                        (${availableChannel.frequency} MHz ${
 | 
				
			||||||
 | 
					                availableChannel.radar ? " ,DFS" : ""
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
                    `;
 | 
					                    `;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,8 +8,8 @@
 | 
				
			|||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import PropTypes from "prop-types";
 | 
					import PropTypes from "prop-types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { CheckBox } from "../../bootstrap/CheckBox";
 | 
					 | 
				
			||||||
import { TextInput } from "../../bootstrap/TextInput";
 | 
					import { TextInput } from "../../bootstrap/TextInput";
 | 
				
			||||||
 | 
					import { Switch } from "../../bootstrap/Switch";
 | 
				
			||||||
import { PasswordInput } from "../../bootstrap/PasswordInput";
 | 
					import { PasswordInput } from "../../bootstrap/PasswordInput";
 | 
				
			||||||
import WiFiQRCode from "./WiFiQRCode";
 | 
					import WiFiQRCode from "./WiFiQRCode";
 | 
				
			||||||
import { HELP_TEXTS } from "./constants";
 | 
					import { HELP_TEXTS } from "./constants";
 | 
				
			||||||
@@ -26,42 +26,44 @@ WifiGuestForm.propTypes = {
 | 
				
			|||||||
        password: PropTypes.string,
 | 
					        password: PropTypes.string,
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
    setFormValue: PropTypes.func.isRequired,
 | 
					    setFormValue: PropTypes.func.isRequired,
 | 
				
			||||||
 | 
					    deviceIndex: PropTypes.string,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function WifiGuestForm({
 | 
					export default function WifiGuestForm({
 | 
				
			||||||
    formData, formErrors, setFormValue, ...props
 | 
					    formData,
 | 
				
			||||||
 | 
					    formErrors,
 | 
				
			||||||
 | 
					    setFormValue,
 | 
				
			||||||
 | 
					    deviceIndex,
 | 
				
			||||||
 | 
					    ...props
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
            <CheckBox
 | 
					            <Switch
 | 
				
			||||||
                label={_("Enable Guest Wifi")}
 | 
					                label={_("Enable Guest Wi-Fi")}
 | 
				
			||||||
                checked={formData.enabled}
 | 
					                checked={formData.enabled}
 | 
				
			||||||
                helpText={HELP_TEXTS.guest_wifi_enabled}
 | 
					                helpText={HELP_TEXTS.guest_wifi_enabled}
 | 
				
			||||||
 | 
					                onChange={setFormValue((value) => ({
 | 
				
			||||||
                onChange={setFormValue(
 | 
					                    devices: {
 | 
				
			||||||
                    (value) => (
 | 
					                        [formData.id]: {
 | 
				
			||||||
                        { devices: { [formData.id]: { guest_wifi: { enabled: { $set: value } } } } }
 | 
					                            guest_wifi: { enabled: { $set: value } },
 | 
				
			||||||
                    ),
 | 
					                        },
 | 
				
			||||||
                )}
 | 
					                    },
 | 
				
			||||||
 | 
					                }))}
 | 
				
			||||||
                {...props}
 | 
					                {...props}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
            {formData.enabled
 | 
					            {formData.enabled ? (
 | 
				
			||||||
                ? (
 | 
					 | 
				
			||||||
                <>
 | 
					                <>
 | 
				
			||||||
                    <TextInput
 | 
					                    <TextInput
 | 
				
			||||||
                        label="SSID"
 | 
					                        label="SSID"
 | 
				
			||||||
                        value={formData.SSID}
 | 
					                        value={formData.SSID}
 | 
				
			||||||
                        error={formErrors.SSID}
 | 
					                        error={formErrors.SSID}
 | 
				
			||||||
 | 
					                        onChange={setFormValue((value) => ({
 | 
				
			||||||
                            onChange={setFormValue(
 | 
					 | 
				
			||||||
                                (value) => ({
 | 
					 | 
				
			||||||
                            devices: {
 | 
					                            devices: {
 | 
				
			||||||
                                        [formData.id]: { guest_wifi: { SSID: { $set: value } } },
 | 
					                                [formData.id]: {
 | 
				
			||||||
 | 
					                                    guest_wifi: { SSID: { $set: value } },
 | 
				
			||||||
                                },
 | 
					                                },
 | 
				
			||||||
                                }),
 | 
					                            },
 | 
				
			||||||
                            )}
 | 
					                        }))}
 | 
				
			||||||
 | 
					 | 
				
			||||||
                        {...props}
 | 
					                        {...props}
 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
                        <div className="input-group-append">
 | 
					                        <div className="input-group-append">
 | 
				
			||||||
@@ -79,22 +81,17 @@ export default function WifiGuestForm({
 | 
				
			|||||||
                        helpText={HELP_TEXTS.password}
 | 
					                        helpText={HELP_TEXTS.password}
 | 
				
			||||||
                        error={formErrors.password}
 | 
					                        error={formErrors.password}
 | 
				
			||||||
                        required
 | 
					                        required
 | 
				
			||||||
 | 
					                        onChange={setFormValue((value) => ({
 | 
				
			||||||
                            onChange={setFormValue(
 | 
					 | 
				
			||||||
                                (value) => ({
 | 
					 | 
				
			||||||
                            devices: {
 | 
					                            devices: {
 | 
				
			||||||
                                [formData.id]: {
 | 
					                                [formData.id]: {
 | 
				
			||||||
                                    guest_wifi: { password: { $set: value } },
 | 
					                                    guest_wifi: { password: { $set: value } },
 | 
				
			||||||
                                },
 | 
					                                },
 | 
				
			||||||
                            },
 | 
					                            },
 | 
				
			||||||
                                }),
 | 
					                        }))}
 | 
				
			||||||
                            )}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        {...props}
 | 
					                        {...props}
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                </>
 | 
					                </>
 | 
				
			||||||
                )
 | 
					            ) : null}
 | 
				
			||||||
                : null}
 | 
					 | 
				
			||||||
        </>
 | 
					        </>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,10 @@ import PropTypes from "prop-types";
 | 
				
			|||||||
import { ForisURLs } from "../../utils/forisUrls";
 | 
					import { ForisURLs } from "../../utils/forisUrls";
 | 
				
			||||||
import { Button } from "../../bootstrap/Button";
 | 
					import { Button } from "../../bootstrap/Button";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    Modal, ModalBody, ModalFooter, ModalHeader,
 | 
					    Modal,
 | 
				
			||||||
 | 
					    ModalBody,
 | 
				
			||||||
 | 
					    ModalFooter,
 | 
				
			||||||
 | 
					    ModalHeader,
 | 
				
			||||||
} from "../../bootstrap/Modal";
 | 
					} from "../../bootstrap/Modal";
 | 
				
			||||||
import { createAndDownloadPdf, toQRCodeContent } from "./qrCodeHelpers";
 | 
					import { createAndDownloadPdf, toQRCodeContent } from "./qrCodeHelpers";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -36,11 +39,21 @@ export default function WiFiQRCode({ SSID, password }) {
 | 
				
			|||||||
                    setModal(true);
 | 
					                    setModal(true);
 | 
				
			||||||
                }}
 | 
					                }}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
                <img width="20" src={QR_ICON_PATH} alt="QR" style={{ opacity: 0.67 }} />
 | 
					                <img
 | 
				
			||||||
 | 
					                    width="20"
 | 
				
			||||||
 | 
					                    src={QR_ICON_PATH}
 | 
				
			||||||
 | 
					                    alt="QR"
 | 
				
			||||||
 | 
					                    style={{ opacity: 0.67 }}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
            </button>
 | 
					            </button>
 | 
				
			||||||
            {modal
 | 
					            {modal ? (
 | 
				
			||||||
                ? <QRCodeModal setShown={setModal} shown={modal} SSID={SSID} password={password} />
 | 
					                <QRCodeModal
 | 
				
			||||||
                : null}
 | 
					                    setShown={setModal}
 | 
				
			||||||
 | 
					                    shown={modal}
 | 
				
			||||||
 | 
					                    SSID={SSID}
 | 
				
			||||||
 | 
					                    password={password}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            ) : null}
 | 
				
			||||||
        </>
 | 
					        </>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -52,9 +65,7 @@ QRCodeModal.propTypes = {
 | 
				
			|||||||
    setShown: PropTypes.func.isRequired,
 | 
					    setShown: PropTypes.func.isRequired,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function QRCodeModal({
 | 
					function QRCodeModal({ shown, setShown, SSID, password }) {
 | 
				
			||||||
    shown, setShown, SSID, password,
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Modal setShown={setShown} shown={shown}>
 | 
					        <Modal setShown={setShown} shown={shown}>
 | 
				
			||||||
            <ModalHeader setShown={setShown} title={_("Wi-Fi QR Code")} />
 | 
					            <ModalHeader setShown={setShown} title={_("Wi-Fi QR Code")} />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,9 +19,7 @@ WiFiSettings.propTypes = {
 | 
				
			|||||||
    hasGuestNetwork: PropTypes.bool,
 | 
					    hasGuestNetwork: PropTypes.bool,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function WiFiSettings({
 | 
					export function WiFiSettings({ ws, endpoint, resetEndpoint, hasGuestNetwork }) {
 | 
				
			||||||
    ws, endpoint, resetEndpoint, hasGuestNetwork,
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
            <ForisForm
 | 
					            <ForisForm
 | 
				
			||||||
@@ -59,35 +57,41 @@ function prepDataToSubmit(formData) {
 | 
				
			|||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!device.guest_wifi.enabled) formData.devices[idx].guest_wifi = { enabled: false };
 | 
					        if (!device.guest_wifi.enabled)
 | 
				
			||||||
 | 
					            formData.devices[idx].guest_wifi = { enabled: false };
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    return formData;
 | 
					    return formData;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function validator(formData) {
 | 
					export function validator(formData) {
 | 
				
			||||||
    const formErrors = formData.devices.map(
 | 
					    const formErrors = formData.devices.map((device) => {
 | 
				
			||||||
        (device) => {
 | 
					 | 
				
			||||||
        if (!device.enabled) return {};
 | 
					        if (!device.enabled) return {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const errors = {};
 | 
					        const errors = {};
 | 
				
			||||||
            if (device.SSID.length > 32) errors.SSID = _("SSID can't be longer than 32 symbols");
 | 
					        if (device.SSID.length > 32)
 | 
				
			||||||
 | 
					            errors.SSID = _("SSID can't be longer than 32 symbols");
 | 
				
			||||||
        if (device.SSID.length === 0) errors.SSID = _("SSID can't be empty");
 | 
					        if (device.SSID.length === 0) errors.SSID = _("SSID can't be empty");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (device.password.length < 8) errors.password = _("Password must contain at least 8 symbols");
 | 
					        if (device.password.length < 8)
 | 
				
			||||||
 | 
					            errors.password = _("Password must contain at least 8 symbols");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!device.guest_wifi.enabled) return errors;
 | 
					        if (!device.guest_wifi.enabled) return errors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const guest_wifi_errors = {};
 | 
					        const guest_wifi_errors = {};
 | 
				
			||||||
            if (device.guest_wifi.SSID.length > 32) guest_wifi_errors.SSID = _("SSID can't be longer than 32 symbols");
 | 
					        if (device.guest_wifi.SSID.length > 32)
 | 
				
			||||||
            if (device.guest_wifi.SSID.length === 0) guest_wifi_errors.SSID = _("SSID can't be empty");
 | 
					            guest_wifi_errors.SSID = _("SSID can't be longer than 32 symbols");
 | 
				
			||||||
 | 
					        if (device.guest_wifi.SSID.length === 0)
 | 
				
			||||||
 | 
					            guest_wifi_errors.SSID = _("SSID can't be empty");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (device.guest_wifi.password.length < 8) guest_wifi_errors.password = _("Password must contain at least 8 symbols");
 | 
					        if (device.guest_wifi.password.length < 8)
 | 
				
			||||||
 | 
					            guest_wifi_errors.password = _(
 | 
				
			||||||
 | 
					                "Password must contain at least 8 symbols"
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (guest_wifi_errors.SSID || guest_wifi_errors.password) {
 | 
					        if (guest_wifi_errors.SSID || guest_wifi_errors.password) {
 | 
				
			||||||
            errors.guest_wifi = guest_wifi_errors;
 | 
					            errors.guest_wifi = guest_wifi_errors;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return errors;
 | 
					        return errors;
 | 
				
			||||||
        },
 | 
					    });
 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    return JSON.stringify(formErrors).match(/\[[{},?]+\]/) ? null : formErrors;
 | 
					    return JSON.stringify(formErrors).match(/\[[{},?]+\]/) ? null : formErrors;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,19 +22,34 @@ describe("<ResetWiFiSettings/>", () => {
 | 
				
			|||||||
    let getAllByText;
 | 
					    let getAllByText;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    beforeEach(() => {
 | 
					    beforeEach(() => {
 | 
				
			||||||
        ({ getAllByText } = render(<ResetWiFiSettings ws={webSockets} endpoint={endpoint} />));
 | 
					        ({ getAllByText } = render(
 | 
				
			||||||
 | 
					            <ResetWiFiSettings ws={webSockets} endpoint={endpoint} />
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("should display alert on open ports - success", async () => {
 | 
					    it("should display alert on open ports - success", async () => {
 | 
				
			||||||
        fireEvent.click(getAllByText("Reset Wi-Fi Settings")[1]);
 | 
					        fireEvent.click(getAllByText("Reset Wi-Fi Settings")[1]);
 | 
				
			||||||
        expect(mockAxios.post).toBeCalledWith(endpoint, undefined, expect.anything());
 | 
					        expect(mockAxios.post).toBeCalledWith(
 | 
				
			||||||
 | 
					            endpoint,
 | 
				
			||||||
 | 
					            undefined,
 | 
				
			||||||
 | 
					            expect.anything()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        mockAxios.mockResponse({ data: { foo: "bar" } });
 | 
					        mockAxios.mockResponse({ data: { foo: "bar" } });
 | 
				
			||||||
        await wait(() => expect(mockSetAlert).toBeCalledWith("Wi-Fi settings are set to defaults.", ALERT_TYPES.SUCCESS));
 | 
					        await wait(() =>
 | 
				
			||||||
 | 
					            expect(mockSetAlert).toBeCalledWith(
 | 
				
			||||||
 | 
					                "Wi-Fi settings are set to defaults.",
 | 
				
			||||||
 | 
					                ALERT_TYPES.SUCCESS
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("should display alert on open ports - failure", async () => {
 | 
					    it("should display alert on open ports - failure", async () => {
 | 
				
			||||||
        fireEvent.click(getAllByText("Reset Wi-Fi Settings")[1]);
 | 
					        fireEvent.click(getAllByText("Reset Wi-Fi Settings")[1]);
 | 
				
			||||||
        mockJSONError();
 | 
					        mockJSONError();
 | 
				
			||||||
        await wait(() => expect(mockSetAlert).toBeCalledWith("An error occurred during resetting Wi-Fi settings."));
 | 
					        await wait(() =>
 | 
				
			||||||
 | 
					            expect(mockSetAlert).toBeCalledWith(
 | 
				
			||||||
 | 
					                "An error occurred during resetting Wi-Fi settings."
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,12 @@ import { fireEvent, render, wait } from "customTestRender";
 | 
				
			|||||||
import { WebSockets } from "webSockets/WebSockets";
 | 
					import { WebSockets } from "webSockets/WebSockets";
 | 
				
			||||||
import { mockJSONError } from "testUtils/network";
 | 
					import { mockJSONError } from "testUtils/network";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { wifiSettingsFixture, oneDevice, twoDevices, threeDevices } from "./__fixtures__/wifiSettings";
 | 
					import {
 | 
				
			||||||
 | 
					    wifiSettingsFixture,
 | 
				
			||||||
 | 
					    oneDevice,
 | 
				
			||||||
 | 
					    twoDevices,
 | 
				
			||||||
 | 
					    threeDevices,
 | 
				
			||||||
 | 
					} from "./__fixtures__/wifiSettings";
 | 
				
			||||||
import { WiFiSettings, validator } from "../WiFiSettings";
 | 
					import { WiFiSettings, validator } from "../WiFiSettings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("<WiFiSettings/>", () => {
 | 
					describe("<WiFiSettings/>", () => {
 | 
				
			||||||
@@ -26,7 +31,13 @@ describe("<WiFiSettings/>", () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    beforeEach(async () => {
 | 
					    beforeEach(async () => {
 | 
				
			||||||
        const webSockets = new WebSockets();
 | 
					        const webSockets = new WebSockets();
 | 
				
			||||||
        const renderRes = render(<WiFiSettings ws={webSockets} endpoint={endpoint} resetEndpoint="foo" />);
 | 
					        const renderRes = render(
 | 
				
			||||||
 | 
					            <WiFiSettings
 | 
				
			||||||
 | 
					                ws={webSockets}
 | 
				
			||||||
 | 
					                endpoint={endpoint}
 | 
				
			||||||
 | 
					                resetEndpoint="foo"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        asFragment = renderRes.asFragment;
 | 
					        asFragment = renderRes.asFragment;
 | 
				
			||||||
        getAllByText = renderRes.getAllByText;
 | 
					        getAllByText = renderRes.getAllByText;
 | 
				
			||||||
        getAllByLabelText = renderRes.getAllByLabelText;
 | 
					        getAllByLabelText = renderRes.getAllByLabelText;
 | 
				
			||||||
@@ -38,7 +49,14 @@ describe("<WiFiSettings/>", () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    it("should handle error", async () => {
 | 
					    it("should handle error", async () => {
 | 
				
			||||||
        const webSockets = new WebSockets();
 | 
					        const webSockets = new WebSockets();
 | 
				
			||||||
        const { getByText } = render(<WiFiSettings ws={webSockets} ws={webSockets} endpoint={endpoint} resetEndpoint="foo" />);
 | 
					        const { getByText } = render(
 | 
				
			||||||
 | 
					            <WiFiSettings
 | 
				
			||||||
 | 
					                ws={webSockets}
 | 
				
			||||||
 | 
					                ws={webSockets}
 | 
				
			||||||
 | 
					                endpoint={endpoint}
 | 
				
			||||||
 | 
					                resetEndpoint="foo"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        const errorMessage = "An API error occurred.";
 | 
					        const errorMessage = "An API error occurred.";
 | 
				
			||||||
        mockJSONError(errorMessage);
 | 
					        mockJSONError(errorMessage);
 | 
				
			||||||
        await wait(() => {
 | 
					        await wait(() => {
 | 
				
			||||||
@@ -51,21 +69,21 @@ describe("<WiFiSettings/>", () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Snapshot one module enabled.", () => {
 | 
					    it("Snapshot one module enabled.", () => {
 | 
				
			||||||
        fireEvent.click(getAllByText("Enable")[0]);
 | 
					        fireEvent.click(getByText("Wi-Fi 1"));
 | 
				
			||||||
        expect(diffSnapshot(firstRender, asFragment())).toMatchSnapshot();
 | 
					        expect(diffSnapshot(firstRender, asFragment())).toMatchSnapshot();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Snapshot 2.4 GHz", () => {
 | 
					    it("Snapshot 2.4 GHz", () => {
 | 
				
			||||||
        fireEvent.click(getAllByText("Enable")[0]);
 | 
					        fireEvent.click(getByText("Wi-Fi 1"));
 | 
				
			||||||
        const enabledRender = asFragment();
 | 
					        const enabledRender = asFragment();
 | 
				
			||||||
        fireEvent.click(getAllByText("2.4")[0]);
 | 
					        fireEvent.click(getAllByText("2.4")[0]);
 | 
				
			||||||
        expect(diffSnapshot(enabledRender, asFragment())).toMatchSnapshot();
 | 
					        expect(diffSnapshot(enabledRender, asFragment())).toMatchSnapshot();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Snapshot guest network.", () => {
 | 
					    it("Snapshot guest network.", () => {
 | 
				
			||||||
        fireEvent.click(getAllByText("Enable")[0]);
 | 
					        fireEvent.click(getByText("Wi-Fi 1"));
 | 
				
			||||||
        const enabledRender = asFragment();
 | 
					        const enabledRender = asFragment();
 | 
				
			||||||
        fireEvent.click(getAllByText("Enable Guest Wifi")[0]);
 | 
					        fireEvent.click(getAllByText("Enable Guest Wi-Fi")[0]);
 | 
				
			||||||
        expect(diffSnapshot(enabledRender, asFragment())).toMatchSnapshot();
 | 
					        expect(diffSnapshot(enabledRender, asFragment())).toMatchSnapshot();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -78,11 +96,15 @@ describe("<WiFiSettings/>", () => {
 | 
				
			|||||||
                { enabled: false, id: 1 },
 | 
					                { enabled: false, id: 1 },
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        expect(mockAxios.post).toHaveBeenCalledWith(endpoint, data, expect.anything());
 | 
					        expect(mockAxios.post).toHaveBeenCalledWith(
 | 
				
			||||||
 | 
					            endpoint,
 | 
				
			||||||
 | 
					            data,
 | 
				
			||||||
 | 
					            expect.anything()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Post form: one module enabled.", () => {
 | 
					    it("Post form: one module enabled.", () => {
 | 
				
			||||||
        fireEvent.click(getAllByText("Enable")[0]);
 | 
					        fireEvent.click(getByText("Wi-Fi 1"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fireEvent.click(getByText("Save"));
 | 
					        fireEvent.click(getByText("Save"));
 | 
				
			||||||
        expect(mockAxios.post).toBeCalled();
 | 
					        expect(mockAxios.post).toBeCalled();
 | 
				
			||||||
@@ -102,11 +124,15 @@ describe("<WiFiSettings/>", () => {
 | 
				
			|||||||
                { enabled: false, id: 1 },
 | 
					                { enabled: false, id: 1 },
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        expect(mockAxios.post).toHaveBeenCalledWith(endpoint, data, expect.anything());
 | 
					        expect(mockAxios.post).toHaveBeenCalledWith(
 | 
				
			||||||
 | 
					            endpoint,
 | 
				
			||||||
 | 
					            data,
 | 
				
			||||||
 | 
					            expect.anything()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Post form: 2.4 GHz", () => {
 | 
					    it("Post form: 2.4 GHz", () => {
 | 
				
			||||||
        fireEvent.click(getAllByText("Enable")[0]);
 | 
					        fireEvent.click(getByText("Wi-Fi 1"));
 | 
				
			||||||
        fireEvent.click(getAllByText("2.4")[0]);
 | 
					        fireEvent.click(getAllByText("2.4")[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fireEvent.click(getByText("Save"));
 | 
					        fireEvent.click(getByText("Save"));
 | 
				
			||||||
@@ -127,13 +153,19 @@ describe("<WiFiSettings/>", () => {
 | 
				
			|||||||
                { enabled: false, id: 1 },
 | 
					                { enabled: false, id: 1 },
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        expect(mockAxios.post).toHaveBeenCalledWith(endpoint, data, expect.anything());
 | 
					        expect(mockAxios.post).toHaveBeenCalledWith(
 | 
				
			||||||
 | 
					            endpoint,
 | 
				
			||||||
 | 
					            data,
 | 
				
			||||||
 | 
					            expect.anything()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Post form: guest network.", () => {
 | 
					    it("Post form: guest network.", () => {
 | 
				
			||||||
        fireEvent.click(getAllByText("Enable")[0]);
 | 
					        fireEvent.click(getByText("Wi-Fi 1"));
 | 
				
			||||||
        fireEvent.click(getAllByText("Enable Guest Wifi")[0]);
 | 
					        fireEvent.click(getAllByText("Enable Guest Wi-Fi")[0]);
 | 
				
			||||||
        fireEvent.change(getAllByLabelText("Password")[1], { target: { value: "test_password" } });
 | 
					        fireEvent.change(getAllByLabelText("Password")[1], {
 | 
				
			||||||
 | 
					            target: { value: "test_password" },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fireEvent.click(getByText("Save"));
 | 
					        fireEvent.click(getByText("Save"));
 | 
				
			||||||
        expect(mockAxios.post).toBeCalled();
 | 
					        expect(mockAxios.post).toBeCalled();
 | 
				
			||||||
@@ -157,7 +189,11 @@ describe("<WiFiSettings/>", () => {
 | 
				
			|||||||
                { enabled: false, id: 1 },
 | 
					                { enabled: false, id: 1 },
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        expect(mockAxios.post).toHaveBeenCalledWith(endpoint, data, expect.anything());
 | 
					        expect(mockAxios.post).toHaveBeenCalledWith(
 | 
				
			||||||
 | 
					            endpoint,
 | 
				
			||||||
 | 
					            data,
 | 
				
			||||||
 | 
					            expect.anything()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Validator function using regex for one device", () => {
 | 
					    it("Validator function using regex for one device", () => {
 | 
				
			||||||
@@ -165,12 +201,16 @@ describe("<WiFiSettings/>", () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Validator function using regex for two devices", () => {
 | 
					    it("Validator function using regex for two devices", () => {
 | 
				
			||||||
        const twoDevicesFormErrors = [{SSID: "SSID can't be empty"}, {}];    
 | 
					        const twoDevicesFormErrors = [{ SSID: "SSID can't be empty" }, {}];
 | 
				
			||||||
        expect(validator(twoDevices)).toEqual(twoDevicesFormErrors);
 | 
					        expect(validator(twoDevices)).toEqual(twoDevicesFormErrors);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Validator function using regex for three devices", () => {
 | 
					    it("Validator function using regex for three devices", () => {
 | 
				
			||||||
        const threeDevicesFormErrors = [{}, {}, {password: "Password must contain at least 8 symbols"}];    
 | 
					        const threeDevicesFormErrors = [
 | 
				
			||||||
 | 
					            {},
 | 
				
			||||||
 | 
					            {},
 | 
				
			||||||
 | 
					            { password: "Password must contain at least 8 symbols" },
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
        expect(validator(threeDevices)).toEqual(threeDevicesFormErrors);
 | 
					        expect(validator(threeDevices)).toEqual(threeDevicesFormErrors);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -292,11 +292,7 @@ export function wifiSettingsFixture() {
 | 
				
			|||||||
                                radar: false,
 | 
					                                radar: false,
 | 
				
			||||||
                            },
 | 
					                            },
 | 
				
			||||||
                        ],
 | 
					                        ],
 | 
				
			||||||
                        available_htmodes: [
 | 
					                        available_htmodes: ["NOHT", "HT20", "HT40"],
 | 
				
			||||||
                            "NOHT",
 | 
					 | 
				
			||||||
                            "HT20",
 | 
					 | 
				
			||||||
                            "HT40",
 | 
					 | 
				
			||||||
                        ],
 | 
					 | 
				
			||||||
                        hwmode: "11g",
 | 
					                        hwmode: "11g",
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                ],
 | 
					                ],
 | 
				
			||||||
@@ -327,9 +323,9 @@ const oneDevice = {
 | 
				
			|||||||
            htmode: "HT40",
 | 
					            htmode: "HT40",
 | 
				
			||||||
            hwmode: "11a",
 | 
					            hwmode: "11a",
 | 
				
			||||||
            id: 0,
 | 
					            id: 0,
 | 
				
			||||||
            password: "TestPass"
 | 
					            password: "TestPass",
 | 
				
			||||||
        }
 | 
					        },
 | 
				
			||||||
    ]
 | 
					    ],
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const twoDevices = {
 | 
					const twoDevices = {
 | 
				
			||||||
@@ -343,7 +339,7 @@ const twoDevices = {
 | 
				
			|||||||
            htmode: "HT40",
 | 
					            htmode: "HT40",
 | 
				
			||||||
            hwmode: "11a",
 | 
					            hwmode: "11a",
 | 
				
			||||||
            id: 0,
 | 
					            id: 0,
 | 
				
			||||||
            password: "TestPass"
 | 
					            password: "TestPass",
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            SSID: "Turris2",
 | 
					            SSID: "Turris2",
 | 
				
			||||||
@@ -354,9 +350,9 @@ const twoDevices = {
 | 
				
			|||||||
            htmode: "HT40",
 | 
					            htmode: "HT40",
 | 
				
			||||||
            hwmode: "11a",
 | 
					            hwmode: "11a",
 | 
				
			||||||
            id: 0,
 | 
					            id: 0,
 | 
				
			||||||
            password: "TestPass"
 | 
					            password: "TestPass",
 | 
				
			||||||
        }
 | 
					        },
 | 
				
			||||||
    ]
 | 
					    ],
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const threeDevices = {
 | 
					const threeDevices = {
 | 
				
			||||||
@@ -370,7 +366,7 @@ const threeDevices = {
 | 
				
			|||||||
            htmode: "HT40",
 | 
					            htmode: "HT40",
 | 
				
			||||||
            hwmode: "11a",
 | 
					            hwmode: "11a",
 | 
				
			||||||
            id: 0,
 | 
					            id: 0,
 | 
				
			||||||
            password: "TestPass"
 | 
					            password: "TestPass",
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            SSID: "Turris2",
 | 
					            SSID: "Turris2",
 | 
				
			||||||
@@ -381,7 +377,7 @@ const threeDevices = {
 | 
				
			|||||||
            htmode: "HT40",
 | 
					            htmode: "HT40",
 | 
				
			||||||
            hwmode: "11a",
 | 
					            hwmode: "11a",
 | 
				
			||||||
            id: 0,
 | 
					            id: 0,
 | 
				
			||||||
            password: "TestPass"
 | 
					            password: "TestPass",
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            SSID: "Turris3",
 | 
					            SSID: "Turris3",
 | 
				
			||||||
@@ -392,9 +388,9 @@ const threeDevices = {
 | 
				
			|||||||
            htmode: "HT40",
 | 
					            htmode: "HT40",
 | 
				
			||||||
            hwmode: "11a",
 | 
					            hwmode: "11a",
 | 
				
			||||||
            id: 0,
 | 
					            id: 0,
 | 
				
			||||||
            password: ""
 | 
					            password: "",
 | 
				
			||||||
        }
 | 
					        },
 | 
				
			||||||
    ]
 | 
					    ],
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export {oneDevice, twoDevices, threeDevices};
 | 
					export { oneDevice, twoDevices, threeDevices };
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@ exports[`<WiFiSettings/> Snapshot 2.4 GHz 1`] = `
 | 
				
			|||||||
- First value
 | 
					- First value
 | 
				
			||||||
+ Second value
 | 
					+ Second value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -246,207 +246,95 @@
 | 
					@@ -245,207 +245,95 @@
 | 
				
			||||||
              value=\\"0\\"
 | 
					              value=\\"0\\"
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
              auto
 | 
					              auto
 | 
				
			||||||
@@ -251,17 +251,14 @@ exports[`<WiFiSettings/> Snapshot 2.4 GHz 1`] = `
 | 
				
			|||||||
exports[`<WiFiSettings/> Snapshot both modules disabled. 1`] = `
 | 
					exports[`<WiFiSettings/> Snapshot both modules disabled. 1`] = `
 | 
				
			||||||
<DocumentFragment>
 | 
					<DocumentFragment>
 | 
				
			||||||
  <div
 | 
					  <div
 | 
				
			||||||
    class="col-sm-12 offset-lg-1 col-lg-10 p-0 mb-3"
 | 
					    class="card p-4 col-sm-12 col-lg-12 p-0 mb-3"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    <form>
 | 
					    <form>
 | 
				
			||||||
      <h3>
 | 
					 | 
				
			||||||
        Wi-Fi 1
 | 
					 | 
				
			||||||
      </h3>
 | 
					 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        class="form-group"
 | 
					        class="form-group"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
          class="custom-control custom-checkbox "
 | 
					          class="custom-control custom-switch custom-control-inline switch-heading"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <input
 | 
					          <input
 | 
				
			||||||
            class="custom-control-input"
 | 
					            class="custom-control-input"
 | 
				
			||||||
@@ -272,18 +269,18 @@ exports[`<WiFiSettings/> Snapshot both modules disabled. 1`] = `
 | 
				
			|||||||
            class="custom-control-label"
 | 
					            class="custom-control-label"
 | 
				
			||||||
            for="1"
 | 
					            for="1"
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            Enable
 | 
					            <h2>
 | 
				
			||||||
 | 
					              Wi-Fi 1
 | 
				
			||||||
 | 
					            </h2>
 | 
				
			||||||
          </label>
 | 
					          </label>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <h3>
 | 
					      <hr />
 | 
				
			||||||
        Wi-Fi 2
 | 
					 | 
				
			||||||
      </h3>
 | 
					 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        class="form-group"
 | 
					        class="form-group"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
          class="custom-control custom-checkbox "
 | 
					          class="custom-control custom-switch custom-control-inline switch-heading"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <input
 | 
					          <input
 | 
				
			||||||
            class="custom-control-input"
 | 
					            class="custom-control-input"
 | 
				
			||||||
@@ -294,7 +291,9 @@ exports[`<WiFiSettings/> Snapshot both modules disabled. 1`] = `
 | 
				
			|||||||
            class="custom-control-label"
 | 
					            class="custom-control-label"
 | 
				
			||||||
            for="2"
 | 
					            for="2"
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            Enable
 | 
					            <h2>
 | 
				
			||||||
 | 
					              Wi-Fi 2
 | 
				
			||||||
 | 
					            </h2>
 | 
				
			||||||
          </label>
 | 
					          </label>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
@@ -302,7 +301,7 @@ exports[`<WiFiSettings/> Snapshot both modules disabled. 1`] = `
 | 
				
			|||||||
        class="text-right"
 | 
					        class="text-right"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <button
 | 
					        <button
 | 
				
			||||||
          class="btn btn-primary  col-sm-12 col-lg-3"
 | 
					          class="btn btn-primary  col-sm-12 col-md-3 col-lg-2"
 | 
				
			||||||
          type="submit"
 | 
					          type="submit"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          Save
 | 
					          Save
 | 
				
			||||||
@@ -310,9 +309,9 @@ exports[`<WiFiSettings/> Snapshot both modules disabled. 1`] = `
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </form>
 | 
					    </form>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  <h4>
 | 
					  <h2>
 | 
				
			||||||
    Reset Wi-Fi Settings
 | 
					    Reset Wi-Fi Settings
 | 
				
			||||||
  </h4>
 | 
					  </h2>
 | 
				
			||||||
  <p>
 | 
					  <p>
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
If a number of wireless cards doesn't match, you may try to reset the Wi-Fi settings. Note that this will remove the
 | 
					If a number of wireless cards doesn't match, you may try to reset the Wi-Fi settings. Note that this will remove the
 | 
				
			||||||
@@ -320,10 +319,10 @@ current Wi-Fi configuration and restore the default values.
 | 
				
			|||||||
        
 | 
					        
 | 
				
			||||||
  </p>
 | 
					  </p>
 | 
				
			||||||
  <div
 | 
					  <div
 | 
				
			||||||
    class="col-sm-12 offset-lg-1 col-lg-10 p-0 mb-3 text-right"
 | 
					    class="col-sm-12 col-lg-12 p-0 mb-3 text-right"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    <button
 | 
					    <button
 | 
				
			||||||
      class="btn btn-warning col-sm-12 col-lg-3"
 | 
					      class="btn btn-warning col-sm-12 col-md-3 col-lg-2"
 | 
				
			||||||
      type="button"
 | 
					      type="button"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      Reset Wi-Fi Settings
 | 
					      Reset Wi-Fi Settings
 | 
				
			||||||
@@ -337,10 +336,10 @@ exports[`<WiFiSettings/> Snapshot guest network. 1`] = `
 | 
				
			|||||||
- First value
 | 
					- First value
 | 
				
			||||||
+ Second value
 | 
					+ Second value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -475,10 +475,89 @@
 | 
					@@ -474,10 +474,89 @@
 | 
				
			||||||
 | 
					          Parameters of the guest network can be set in the Guest network tab.
 | 
				
			||||||
          
 | 
					          
 | 
				
			||||||
            </small>
 | 
					            </small>
 | 
				
			||||||
            </label>
 | 
					 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
+       <div
 | 
					+       <div
 | 
				
			||||||
@@ -422,17 +421,17 @@ exports[`<WiFiSettings/> Snapshot guest network. 1`] = `
 | 
				
			|||||||
+     
 | 
					+     
 | 
				
			||||||
+         </small>
 | 
					+         </small>
 | 
				
			||||||
+       </div>
 | 
					+       </div>
 | 
				
			||||||
        <h3>
 | 
					        <hr />
 | 
				
			||||||
          Wi-Fi 2
 | 
					 | 
				
			||||||
        </h3>
 | 
					 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
          class=\\"form-group\\"
 | 
					          class=\\"form-group\\"
 | 
				
			||||||
@@ -502,10 +581,11 @@
 | 
					        >
 | 
				
			||||||
 | 
					          <div
 | 
				
			||||||
 | 
					@@ -501,10 +580,11 @@
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
          class=\\"text-right\\"
 | 
					          class=\\"text-right\\"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <button
 | 
					          <button
 | 
				
			||||||
            class=\\"btn btn-primary  col-sm-12 col-lg-3\\"
 | 
					            class=\\"btn btn-primary  col-sm-12 col-md-3 col-lg-2\\"
 | 
				
			||||||
+           disabled=\\"\\"
 | 
					+           disabled=\\"\\"
 | 
				
			||||||
            type=\\"submit\\"
 | 
					            type=\\"submit\\"
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
@@ -446,9 +445,9 @@ exports[`<WiFiSettings/> Snapshot one module enabled. 1`] = `
 | 
				
			|||||||
- First value
 | 
					- First value
 | 
				
			||||||
+ Second value
 | 
					+ Second value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -23,10 +23,462 @@
 | 
					@@ -22,10 +22,462 @@
 | 
				
			||||||
            >
 | 
					                Wi-Fi 1
 | 
				
			||||||
              Enable
 | 
					              </h2>
 | 
				
			||||||
            </label>
 | 
					            </label>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
@@ -880,7 +879,7 @@ exports[`<WiFiSettings/> Snapshot one module enabled. 1`] = `
 | 
				
			|||||||
+         class=\\"form-group\\"
 | 
					+         class=\\"form-group\\"
 | 
				
			||||||
+       >
 | 
					+       >
 | 
				
			||||||
+         <div
 | 
					+         <div
 | 
				
			||||||
+           class=\\"custom-control custom-checkbox \\"
 | 
					+           class=\\"custom-control custom-switch\\"
 | 
				
			||||||
+         >
 | 
					+         >
 | 
				
			||||||
+           <input
 | 
					+           <input
 | 
				
			||||||
+             class=\\"custom-control-input\\"
 | 
					+             class=\\"custom-control-input\\"
 | 
				
			||||||
@@ -891,9 +890,10 @@ exports[`<WiFiSettings/> Snapshot one module enabled. 1`] = `
 | 
				
			|||||||
+             class=\\"custom-control-label\\"
 | 
					+             class=\\"custom-control-label\\"
 | 
				
			||||||
+             for=\\"10\\"
 | 
					+             for=\\"10\\"
 | 
				
			||||||
+           >
 | 
					+           >
 | 
				
			||||||
+             Enable Guest Wifi
 | 
					+             Enable Guest Wi-Fi
 | 
				
			||||||
 | 
					+           </label>
 | 
				
			||||||
+           <small
 | 
					+           <small
 | 
				
			||||||
+               class=\\"form-text text-muted\\"
 | 
					+             class=\\"form-text text-muted mt-0 mb-3\\"
 | 
				
			||||||
+           >
 | 
					+           >
 | 
				
			||||||
+             
 | 
					+             
 | 
				
			||||||
+         Enables Wi-Fi for guests, which is separated from LAN network. Devices connected to this network are allowed to
 | 
					+         Enables Wi-Fi for guests, which is separated from LAN network. Devices connected to this network are allowed to
 | 
				
			||||||
@@ -901,12 +901,11 @@ exports[`<WiFiSettings/> Snapshot one module enabled. 1`] = `
 | 
				
			|||||||
+         Parameters of the guest network can be set in the Guest network tab.
 | 
					+         Parameters of the guest network can be set in the Guest network tab.
 | 
				
			||||||
+         
 | 
					+         
 | 
				
			||||||
+           </small>
 | 
					+           </small>
 | 
				
			||||||
+           </label>
 | 
					 | 
				
			||||||
+         </div>
 | 
					+         </div>
 | 
				
			||||||
+       </div>
 | 
					+       </div>
 | 
				
			||||||
        <h3>
 | 
					        <hr />
 | 
				
			||||||
          Wi-Fi 2
 | 
					 | 
				
			||||||
        </h3>
 | 
					 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
          class=\\"form-group\\""
 | 
					          class=\\"form-group\\"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <div"
 | 
				
			||||||
`;
 | 
					`;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,7 +22,9 @@ export const HELP_TEXTS = {
 | 
				
			|||||||
    password: _(`
 | 
					    password: _(`
 | 
				
			||||||
        WPA2 pre-shared key, that is required to connect to the network.
 | 
					        WPA2 pre-shared key, that is required to connect to the network.
 | 
				
			||||||
    `),
 | 
					    `),
 | 
				
			||||||
    hidden: _("If set, network is not visible when scanning for available networks."),
 | 
					    hidden: _(
 | 
				
			||||||
 | 
					        "If set, network is not visible when scanning for available networks."
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
    hwmode: _(`
 | 
					    hwmode: _(`
 | 
				
			||||||
        The 2.4 GHz band is more widely supported by clients, but tends to have more interference. The 5 GHz band is a
 | 
					        The 2.4 GHz band is more widely supported by clients, but tends to have more interference. The 5 GHz band is a
 | 
				
			||||||
        newer standard and may not be supported by all your devices. It usually has less interference, but the signal
 | 
					        newer standard and may not be supported by all your devices. It usually has less interference, but the signal
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,11 @@
 | 
				
			|||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    fireEvent, getByText, queryByText, render, wait,
 | 
					    fireEvent,
 | 
				
			||||||
 | 
					    getByText,
 | 
				
			||||||
 | 
					    queryByText,
 | 
				
			||||||
 | 
					    render,
 | 
				
			||||||
 | 
					    wait,
 | 
				
			||||||
} from "customTestRender";
 | 
					} from "customTestRender";
 | 
				
			||||||
import mockAxios from "jest-mock-axios";
 | 
					import mockAxios from "jest-mock-axios";
 | 
				
			||||||
import { mockJSONError } from "testUtils/network";
 | 
					import { mockJSONError } from "testUtils/network";
 | 
				
			||||||
@@ -19,38 +23,41 @@ import { RebootButton } from "../RebootButton";
 | 
				
			|||||||
describe("<RebootButton/>", () => {
 | 
					describe("<RebootButton/>", () => {
 | 
				
			||||||
    let componentContainer;
 | 
					    let componentContainer;
 | 
				
			||||||
    beforeEach(() => {
 | 
					    beforeEach(() => {
 | 
				
			||||||
        const { container } = render(<>
 | 
					        const { container } = render(
 | 
				
			||||||
 | 
					            <>
 | 
				
			||||||
                <div id="modal-container" />
 | 
					                <div id="modal-container" />
 | 
				
			||||||
                <RebootButton />
 | 
					                <RebootButton />
 | 
				
			||||||
        </>);
 | 
					            </>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        componentContainer = container;
 | 
					        componentContainer = container;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Render.", () => {
 | 
					    it("Render.", () => {
 | 
				
			||||||
        expect(componentContainer)
 | 
					        expect(componentContainer).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Render modal.", () => {
 | 
					    it("Render modal.", () => {
 | 
				
			||||||
        expect(queryByText(componentContainer, "Confirm reboot"))
 | 
					        expect(queryByText(componentContainer, "Confirm reboot")).toBeNull();
 | 
				
			||||||
            .toBeNull();
 | 
					 | 
				
			||||||
        fireEvent.click(getByText(componentContainer, "Reboot"));
 | 
					        fireEvent.click(getByText(componentContainer, "Reboot"));
 | 
				
			||||||
        expect(componentContainer)
 | 
					        expect(componentContainer).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Confirm reboot.", () => {
 | 
					    it("Confirm reboot.", () => {
 | 
				
			||||||
        fireEvent.click(getByText(componentContainer, "Reboot"));
 | 
					        fireEvent.click(getByText(componentContainer, "Reboot"));
 | 
				
			||||||
        fireEvent.click(getByText(componentContainer, "Confirm reboot"));
 | 
					        fireEvent.click(getByText(componentContainer, "Confirm reboot"));
 | 
				
			||||||
        expect(mockAxios.post)
 | 
					        expect(mockAxios.post).toHaveBeenCalledWith(
 | 
				
			||||||
            .toHaveBeenCalledWith("/reforis/api/reboot", undefined, expect.anything());
 | 
					            "/reforis/api/reboot",
 | 
				
			||||||
 | 
					            undefined,
 | 
				
			||||||
 | 
					            expect.anything()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Hold error.", async () => {
 | 
					    it("Hold error.", async () => {
 | 
				
			||||||
        fireEvent.click(getByText(componentContainer, "Reboot"));
 | 
					        fireEvent.click(getByText(componentContainer, "Reboot"));
 | 
				
			||||||
        fireEvent.click(getByText(componentContainer, "Confirm reboot"));
 | 
					        fireEvent.click(getByText(componentContainer, "Confirm reboot"));
 | 
				
			||||||
        mockJSONError();
 | 
					        mockJSONError();
 | 
				
			||||||
        await wait(() => expect(mockSetAlert)
 | 
					        await wait(() =>
 | 
				
			||||||
            .toBeCalledWith("Reboot request failed."));
 | 
					            expect(mockSetAlert).toBeCalledWith("Reboot request failed.")
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,16 +49,12 @@ exports[`<RebootButton/> Render modal. 1`] = `
 | 
				
			|||||||
              class="btn btn-primary "
 | 
					              class="btn btn-primary "
 | 
				
			||||||
              type="button"
 | 
					              type="button"
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
               
 | 
					 | 
				
			||||||
               
 | 
					 | 
				
			||||||
              Cancel
 | 
					              Cancel
 | 
				
			||||||
            </button>
 | 
					            </button>
 | 
				
			||||||
            <button
 | 
					            <button
 | 
				
			||||||
              class="btn btn-danger"
 | 
					              class="btn btn-danger"
 | 
				
			||||||
              type="button"
 | 
					              type="button"
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
               
 | 
					 | 
				
			||||||
               
 | 
					 | 
				
			||||||
              Confirm reboot
 | 
					              Confirm reboot
 | 
				
			||||||
            </button>
 | 
					            </button>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
@@ -70,8 +66,6 @@ exports[`<RebootButton/> Render modal. 1`] = `
 | 
				
			|||||||
    class="btn btn-danger"
 | 
					    class="btn btn-danger"
 | 
				
			||||||
    type="button"
 | 
					    type="button"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
     
 | 
					 | 
				
			||||||
     
 | 
					 | 
				
			||||||
    Reboot
 | 
					    Reboot
 | 
				
			||||||
  </button>
 | 
					  </button>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
@@ -86,8 +80,6 @@ exports[`<RebootButton/> Render. 1`] = `
 | 
				
			|||||||
    class="btn btn-danger"
 | 
					    class="btn btn-danger"
 | 
				
			||||||
    type="button"
 | 
					    type="button"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
     
 | 
					 | 
				
			||||||
     
 | 
					 | 
				
			||||||
    Reboot
 | 
					    Reboot
 | 
				
			||||||
  </button>
 | 
					  </button>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,17 +13,14 @@ import { STATES, SubmitButton } from "../components/SubmitButton";
 | 
				
			|||||||
describe("<SubmitButton/>", () => {
 | 
					describe("<SubmitButton/>", () => {
 | 
				
			||||||
    it("Render ready", () => {
 | 
					    it("Render ready", () => {
 | 
				
			||||||
        const { container } = render(<SubmitButton state={STATES.READY} />);
 | 
					        const { container } = render(<SubmitButton state={STATES.READY} />);
 | 
				
			||||||
        expect(container)
 | 
					        expect(container).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    it("Render saving", () => {
 | 
					    it("Render saving", () => {
 | 
				
			||||||
        const { container } = render(<SubmitButton state={STATES.SAVING} />);
 | 
					        const { container } = render(<SubmitButton state={STATES.SAVING} />);
 | 
				
			||||||
        expect(container)
 | 
					        expect(container).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    it("Render load", () => {
 | 
					    it("Render load", () => {
 | 
				
			||||||
        const { container } = render(<SubmitButton state={STATES.LOAD} />);
 | 
					        const { container } = render(<SubmitButton state={STATES.LOAD} />);
 | 
				
			||||||
        expect(container)
 | 
					        expect(container).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@
 | 
				
			|||||||
exports[`<SubmitButton/> Render load 1`] = `
 | 
					exports[`<SubmitButton/> Render load 1`] = `
 | 
				
			||||||
<div>
 | 
					<div>
 | 
				
			||||||
  <button
 | 
					  <button
 | 
				
			||||||
    class="btn btn-primary  col-sm-12 col-lg-3"
 | 
					    class="btn btn-primary  col-sm-12 col-md-3 col-lg-2"
 | 
				
			||||||
    disabled=""
 | 
					    disabled=""
 | 
				
			||||||
    type="submit"
 | 
					    type="submit"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
@@ -13,8 +13,6 @@ exports[`<SubmitButton/> Render load 1`] = `
 | 
				
			|||||||
      role="status"
 | 
					      role="status"
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
     
 | 
					     
 | 
				
			||||||
     
 | 
					 | 
				
			||||||
     
 | 
					 | 
				
			||||||
    Load settings
 | 
					    Load settings
 | 
				
			||||||
  </button>
 | 
					  </button>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
@@ -23,11 +21,9 @@ exports[`<SubmitButton/> Render load 1`] = `
 | 
				
			|||||||
exports[`<SubmitButton/> Render ready 1`] = `
 | 
					exports[`<SubmitButton/> Render ready 1`] = `
 | 
				
			||||||
<div>
 | 
					<div>
 | 
				
			||||||
  <button
 | 
					  <button
 | 
				
			||||||
    class="btn btn-primary  col-sm-12 col-lg-3"
 | 
					    class="btn btn-primary  col-sm-12 col-md-3 col-lg-2"
 | 
				
			||||||
    type="submit"
 | 
					    type="submit"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
     
 | 
					 | 
				
			||||||
     
 | 
					 | 
				
			||||||
    Save
 | 
					    Save
 | 
				
			||||||
  </button>
 | 
					  </button>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
@@ -36,7 +32,7 @@ exports[`<SubmitButton/> Render ready 1`] = `
 | 
				
			|||||||
exports[`<SubmitButton/> Render saving 1`] = `
 | 
					exports[`<SubmitButton/> Render saving 1`] = `
 | 
				
			||||||
<div>
 | 
					<div>
 | 
				
			||||||
  <button
 | 
					  <button
 | 
				
			||||||
    class="btn btn-primary  col-sm-12 col-lg-3"
 | 
					    class="btn btn-primary  col-sm-12 col-md-3 col-lg-2"
 | 
				
			||||||
    disabled=""
 | 
					    disabled=""
 | 
				
			||||||
    type="submit"
 | 
					    type="submit"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
@@ -46,8 +42,6 @@ exports[`<SubmitButton/> Render saving 1`] = `
 | 
				
			|||||||
      role="status"
 | 
					      role="status"
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
     
 | 
					     
 | 
				
			||||||
     
 | 
					 | 
				
			||||||
     
 | 
					 | 
				
			||||||
    Updating
 | 
					    Updating
 | 
				
			||||||
  </button>
 | 
					  </button>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,9 +7,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import { act, fireEvent, render, waitForElement } from "customTestRender";
 | 
				
			||||||
    act, fireEvent, render, waitForElement,
 | 
					 | 
				
			||||||
} from "customTestRender";
 | 
					 | 
				
			||||||
import mockAxios from "jest-mock-axios";
 | 
					import mockAxios from "jest-mock-axios";
 | 
				
			||||||
import { WebSockets } from "webSockets/WebSockets";
 | 
					import { WebSockets } from "webSockets/WebSockets";
 | 
				
			||||||
import { ForisForm } from "../components/ForisForm";
 | 
					import { ForisForm } from "../components/ForisForm";
 | 
				
			||||||
@@ -38,8 +36,12 @@ describe("useForm hook.", () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    beforeEach(async () => {
 | 
					    beforeEach(async () => {
 | 
				
			||||||
        mockPrepData = jest.fn(() => ({ field: "preparedData" }));
 | 
					        mockPrepData = jest.fn(() => ({ field: "preparedData" }));
 | 
				
			||||||
        mockPrepDataToSubmit = jest.fn(() => ({ field: "preparedDataToSubmit" }));
 | 
					        mockPrepDataToSubmit = jest.fn(() => ({
 | 
				
			||||||
        mockValidator = jest.fn((data) => (data.field === "invalidValue" ? { field: "Error" } : {}));
 | 
					            field: "preparedDataToSubmit",
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					        mockValidator = jest.fn((data) =>
 | 
				
			||||||
 | 
					            data.field === "invalidValue" ? { field: "Error" } : {}
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        const { getByTestId, container } = render(
 | 
					        const { getByTestId, container } = render(
 | 
				
			||||||
            <ForisForm
 | 
					            <ForisForm
 | 
				
			||||||
                ws={new WebSockets()}
 | 
					                ws={new WebSockets()}
 | 
				
			||||||
@@ -53,7 +55,7 @@ describe("useForm hook.", () => {
 | 
				
			|||||||
                validator={mockValidator}
 | 
					                validator={mockValidator}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
                <Child />
 | 
					                <Child />
 | 
				
			||||||
            </ForisForm>,
 | 
					            </ForisForm>
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        mockAxios.mockResponse({ field: "fetchedData" });
 | 
					        mockAxios.mockResponse({ field: "fetchedData" });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -67,16 +69,22 @@ describe("useForm hook.", () => {
 | 
				
			|||||||
        expect(Child.mock.calls[0][0].formErrors).toMatchObject({});
 | 
					        expect(Child.mock.calls[0][0].formErrors).toMatchObject({});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        act(() => {
 | 
					        act(() => {
 | 
				
			||||||
            fireEvent.change(input, { target: { value: "invalidValue", type: "text" } });
 | 
					            fireEvent.change(input, {
 | 
				
			||||||
 | 
					                target: { value: "invalidValue", type: "text" },
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(Child).toHaveBeenCalledTimes(2);
 | 
					        expect(Child).toHaveBeenCalledTimes(2);
 | 
				
			||||||
        expect(mockValidator).toHaveBeenCalledTimes(2);
 | 
					        expect(mockValidator).toHaveBeenCalledTimes(2);
 | 
				
			||||||
        expect(Child.mock.calls[1][0].formErrors).toMatchObject({ field: "Error" });
 | 
					        expect(Child.mock.calls[1][0].formErrors).toMatchObject({
 | 
				
			||||||
 | 
					            field: "Error",
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Update text value.", () => {
 | 
					    it("Update text value.", () => {
 | 
				
			||||||
        fireEvent.change(input, { target: { value: "newValue", type: "text" } });
 | 
					        fireEvent.change(input, {
 | 
				
			||||||
 | 
					            target: { value: "newValue", type: "text" },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
        expect(input.value).toBe("newValue");
 | 
					        expect(input.value).toBe("newValue");
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -86,14 +94,21 @@ describe("useForm hook.", () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Update checkbox value.", () => {
 | 
					    it("Update checkbox value.", () => {
 | 
				
			||||||
        fireEvent.change(input, { target: { checked: true, type: "checkbox" } });
 | 
					        fireEvent.change(input, {
 | 
				
			||||||
 | 
					            target: { checked: true, type: "checkbox" },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
        expect(input.checked).toBe(true);
 | 
					        expect(input.checked).toBe(true);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Fetch data.", () => {
 | 
					    it("Fetch data.", () => {
 | 
				
			||||||
        expect(mockAxios.get).toHaveBeenCalledWith("testEndpoint", expect.anything());
 | 
					        expect(mockAxios.get).toHaveBeenCalledWith(
 | 
				
			||||||
 | 
					            "testEndpoint",
 | 
				
			||||||
 | 
					            expect.anything()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        expect(mockPrepData).toHaveBeenCalledTimes(1);
 | 
					        expect(mockPrepData).toHaveBeenCalledTimes(1);
 | 
				
			||||||
        expect(Child.mock.calls[0][0].formData).toMatchObject({ field: "preparedData" });
 | 
					        expect(Child.mock.calls[0][0].formData).toMatchObject({
 | 
				
			||||||
 | 
					            field: "preparedData",
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Submit.", () => {
 | 
					    it("Submit.", () => {
 | 
				
			||||||
@@ -107,7 +122,7 @@ describe("useForm hook.", () => {
 | 
				
			|||||||
        expect(mockAxios.post).toHaveBeenCalledWith(
 | 
					        expect(mockAxios.post).toHaveBeenCalledWith(
 | 
				
			||||||
            "testEndpoint",
 | 
					            "testEndpoint",
 | 
				
			||||||
            { field: "preparedDataToSubmit" },
 | 
					            { field: "preparedDataToSubmit" },
 | 
				
			||||||
            expect.anything(),
 | 
					            expect.anything()
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,120 +16,75 @@ import {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
describe("Validation functions", () => {
 | 
					describe("Validation functions", () => {
 | 
				
			||||||
    it("validateIPv4Address valid", () => {
 | 
					    it("validateIPv4Address valid", () => {
 | 
				
			||||||
        expect(validateIPv4Address("192.168.1.1"))
 | 
					        expect(validateIPv4Address("192.168.1.1")).toBe(undefined);
 | 
				
			||||||
            .toBe(undefined);
 | 
					        expect(validateIPv4Address("1.1.1.1")).toBe(undefined);
 | 
				
			||||||
        expect(validateIPv4Address("1.1.1.1"))
 | 
					        expect(validateIPv4Address("0.0.0.0")).toBe(undefined);
 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
        expect(validateIPv4Address("0.0.0.0"))
 | 
					 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    it("validateIPv4Address invalid", () => {
 | 
					    it("validateIPv4Address invalid", () => {
 | 
				
			||||||
        expect(validateIPv4Address("invalid"))
 | 
					        expect(validateIPv4Address("invalid")).not.toBe(undefined);
 | 
				
			||||||
            .not
 | 
					        expect(validateIPv4Address("192.256.1.1")).not.toBe(undefined);
 | 
				
			||||||
            .toBe(undefined);
 | 
					        expect(validateIPv4Address("192.168.256.1")).not.toBe(undefined);
 | 
				
			||||||
        expect(validateIPv4Address("192.256.1.1"))
 | 
					        expect(validateIPv4Address("192.168.1.256")).not.toBe(undefined);
 | 
				
			||||||
            .not
 | 
					        expect(validateIPv4Address("192.168.1.256")).not.toBe(undefined);
 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
        expect(validateIPv4Address("192.168.256.1"))
 | 
					 | 
				
			||||||
            .not
 | 
					 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
        expect(validateIPv4Address("192.168.1.256"))
 | 
					 | 
				
			||||||
            .not
 | 
					 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
        expect(validateIPv4Address("192.168.1.256"))
 | 
					 | 
				
			||||||
            .not
 | 
					 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("validateIPv6Address valid", () => {
 | 
					    it("validateIPv6Address valid", () => {
 | 
				
			||||||
        expect(validateIPv6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334"))
 | 
					        expect(
 | 
				
			||||||
            .toBe(undefined);
 | 
					            validateIPv6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
 | 
				
			||||||
        expect(validateIPv6Address("0:0:0:0:0:0:0:1"))
 | 
					        ).toBe(undefined);
 | 
				
			||||||
            .toBe(undefined);
 | 
					        expect(validateIPv6Address("0:0:0:0:0:0:0:1")).toBe(undefined);
 | 
				
			||||||
        expect(validateIPv6Address("::1"))
 | 
					        expect(validateIPv6Address("::1")).toBe(undefined);
 | 
				
			||||||
            .toBe(undefined);
 | 
					        expect(validateIPv6Address("::")).toBe(undefined);
 | 
				
			||||||
        expect(validateIPv6Address("::"))
 | 
					 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    it("validateIPv6Address invalid", () => {
 | 
					    it("validateIPv6Address invalid", () => {
 | 
				
			||||||
        expect(validateIPv6Address("invalid"))
 | 
					        expect(validateIPv6Address("invalid")).not.toBe(undefined);
 | 
				
			||||||
            .not
 | 
					        expect(validateIPv6Address("1.1.1.1")).not.toBe(undefined);
 | 
				
			||||||
            .toBe(undefined);
 | 
					        expect(validateIPv6Address("1200::AB00:1234::2552:7777:1313")).not.toBe(
 | 
				
			||||||
        expect(validateIPv6Address("1.1.1.1"))
 | 
					            undefined
 | 
				
			||||||
            .not
 | 
					        );
 | 
				
			||||||
            .toBe(undefined);
 | 
					        expect(
 | 
				
			||||||
        expect(validateIPv6Address("1200::AB00:1234::2552:7777:1313"))
 | 
					            validateIPv6Address("1200:0000:AB00:1234:O000:2552:7777:1313")
 | 
				
			||||||
            .not
 | 
					        ).not.toBe(undefined);
 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
        expect(validateIPv6Address("1200:0000:AB00:1234:O000:2552:7777:1313"))
 | 
					 | 
				
			||||||
            .not
 | 
					 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("validateIPv6Prefix valid", () => {
 | 
					    it("validateIPv6Prefix valid", () => {
 | 
				
			||||||
        expect(validateIPv6Prefix("2002:0000::/16"))
 | 
					        expect(validateIPv6Prefix("2002:0000::/16")).toBe(undefined);
 | 
				
			||||||
            .toBe(undefined);
 | 
					        expect(validateIPv6Prefix("0::/0")).toBe(undefined);
 | 
				
			||||||
        expect(validateIPv6Prefix("0::/0"))
 | 
					 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    it("validateIPv6Prefix invalid", () => {
 | 
					    it("validateIPv6Prefix invalid", () => {
 | 
				
			||||||
        expect(validateIPv6Prefix("2001:0db8:85a3:0000:0000:8a2e:0370:7334"))
 | 
					        expect(
 | 
				
			||||||
            .not
 | 
					            validateIPv6Prefix("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
 | 
				
			||||||
            .toBe(undefined);
 | 
					        ).not.toBe(undefined);
 | 
				
			||||||
        expect(validateIPv6Prefix("::1"))
 | 
					        expect(validateIPv6Prefix("::1")).not.toBe(undefined);
 | 
				
			||||||
            .not
 | 
					        expect(validateIPv6Prefix("2002:0000::/999")).not.toBe(undefined);
 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
        expect(validateIPv6Prefix("2002:0000::/999"))
 | 
					 | 
				
			||||||
            .not
 | 
					 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("validateDomain valid", () => {
 | 
					    it("validateDomain valid", () => {
 | 
				
			||||||
        expect(validateDomain("example.com"))
 | 
					        expect(validateDomain("example.com")).toBe(undefined);
 | 
				
			||||||
            .toBe(undefined);
 | 
					        expect(validateDomain("one.two.three")).toBe(undefined);
 | 
				
			||||||
        expect(validateDomain("one.two.three"))
 | 
					 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    it("validateDomain invalid", () => {
 | 
					    it("validateDomain invalid", () => {
 | 
				
			||||||
        expect(validateDomain("test/"))
 | 
					        expect(validateDomain("test/")).not.toBe(undefined);
 | 
				
			||||||
            .not
 | 
					        expect(validateDomain(".")).not.toBe(undefined);
 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
        expect(validateDomain("."))
 | 
					 | 
				
			||||||
            .not
 | 
					 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("validateDUID valid", () => {
 | 
					    it("validateDUID valid", () => {
 | 
				
			||||||
        expect(validateDUID("abcdefAB"))
 | 
					        expect(validateDUID("abcdefAB")).toBe(undefined);
 | 
				
			||||||
            .toBe(undefined);
 | 
					        expect(validateDUID("ABCDEF12")).toBe(undefined);
 | 
				
			||||||
        expect(validateDUID("ABCDEF12"))
 | 
					        expect(validateDUID("ABCDEF12AB")).toBe(undefined);
 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
        expect(validateDUID("ABCDEF12AB"))
 | 
					 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    it("validateDUID invalid", () => {
 | 
					    it("validateDUID invalid", () => {
 | 
				
			||||||
        expect(validateDUID("gggggggg"))
 | 
					        expect(validateDUID("gggggggg")).not.toBe(undefined);
 | 
				
			||||||
            .not
 | 
					        expect(validateDUID("abcdefABa")).not.toBe(undefined);
 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
        expect(validateDUID("abcdefABa"))
 | 
					 | 
				
			||||||
            .not
 | 
					 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("validateMAC valid", () => {
 | 
					    it("validateMAC valid", () => {
 | 
				
			||||||
        expect(validateMAC("00:D0:56:F2:B5:12"))
 | 
					        expect(validateMAC("00:D0:56:F2:B5:12")).toBe(undefined);
 | 
				
			||||||
            .toBe(undefined);
 | 
					        expect(validateMAC("00:26:DD:14:C4:EE")).toBe(undefined);
 | 
				
			||||||
        expect(validateMAC("00:26:DD:14:C4:EE"))
 | 
					        expect(validateMAC("06:00:00:00:00:00")).toBe(undefined);
 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
        expect(validateMAC("06:00:00:00:00:00"))
 | 
					 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    it("validateMAC invalid", () => {
 | 
					    it("validateMAC invalid", () => {
 | 
				
			||||||
        expect(validateMAC("00:00:00:00:00:0G"))
 | 
					        expect(validateMAC("00:00:00:00:00:0G")).not.toBe(undefined);
 | 
				
			||||||
            .not
 | 
					        expect(validateMAC("06:00:00:00:00:00:00")).not.toBe(undefined);
 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
        expect(validateMAC("06:00:00:00:00:00:00"))
 | 
					 | 
				
			||||||
            .not
 | 
					 | 
				
			||||||
            .toBe(undefined);
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -59,12 +59,18 @@ ForisForm.propTypes = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // eslint-disable-next-line react/no-unused-prop-types
 | 
					    // eslint-disable-next-line react/no-unused-prop-types
 | 
				
			||||||
    customWSProp(props) {
 | 
					    customWSProp(props) {
 | 
				
			||||||
        const wsModuleIsSpecified = !!(props.forisConfig && props.forisConfig.wsModule);
 | 
					        const wsModuleIsSpecified = !!(
 | 
				
			||||||
 | 
					            props.forisConfig && props.forisConfig.wsModule
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        if (props.ws && !wsModuleIsSpecified) {
 | 
					        if (props.ws && !wsModuleIsSpecified) {
 | 
				
			||||||
            return new Error("forisConfig.wsModule should be specified when ws object is passed.");
 | 
					            return new Error(
 | 
				
			||||||
 | 
					                "forisConfig.wsModule should be specified when ws object is passed."
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (!props.ws && wsModuleIsSpecified) {
 | 
					        if (!props.ws && wsModuleIsSpecified) {
 | 
				
			||||||
            return new Error("forisConfig.wsModule is specified without passing ws object.");
 | 
					            return new Error(
 | 
				
			||||||
 | 
					                "forisConfig.wsModule is specified without passing ws object."
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -95,7 +101,10 @@ export function ForisForm({
 | 
				
			|||||||
    formReference,
 | 
					    formReference,
 | 
				
			||||||
    children,
 | 
					    children,
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
    const [formState, onFormChangeHandler, resetFormData] = useForm(validator, prepData);
 | 
					    const [formState, onFormChangeHandler, resetFormData] = useForm(
 | 
				
			||||||
 | 
					        validator,
 | 
				
			||||||
 | 
					        prepData
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    const [setAlert, dismissAlert] = useAlert();
 | 
					    const [setAlert, dismissAlert] = useAlert();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const [forisModuleState] = useForisModule(ws, forisConfig);
 | 
					    const [forisModuleState] = useForisModule(ws, forisConfig);
 | 
				
			||||||
@@ -141,29 +150,39 @@ export function ForisForm({
 | 
				
			|||||||
        return SUBMIT_BUTTON_STATES.READY;
 | 
					        return SUBMIT_BUTTON_STATES.READY;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const formIsDisabled = (disabled
 | 
					    const formIsDisabled =
 | 
				
			||||||
        || forisModuleState.state === API_STATE.SENDING
 | 
					        disabled ||
 | 
				
			||||||
        || postState.state === API_STATE.SENDING);
 | 
					        forisModuleState.state === API_STATE.SENDING ||
 | 
				
			||||||
 | 
					        postState.state === API_STATE.SENDING;
 | 
				
			||||||
    const submitButtonIsDisabled = disabled || !!formState.errors;
 | 
					    const submitButtonIsDisabled = disabled || !!formState.errors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const childrenWithFormProps = React.Children.map(
 | 
					    const childrenWithFormProps = React.Children.map(children, (child) =>
 | 
				
			||||||
        children,
 | 
					        React.cloneElement(child, {
 | 
				
			||||||
        (child) => React.cloneElement(child, {
 | 
					 | 
				
			||||||
            initialData: formState.initialData,
 | 
					            initialData: formState.initialData,
 | 
				
			||||||
            formData: formState.data,
 | 
					            formData: formState.data,
 | 
				
			||||||
            formErrors: formState.errors,
 | 
					            formErrors: formState.errors,
 | 
				
			||||||
            setFormValue: onFormChangeHandler,
 | 
					            setFormValue: onFormChangeHandler,
 | 
				
			||||||
            disabled: formIsDisabled,
 | 
					            disabled: formIsDisabled,
 | 
				
			||||||
        }),
 | 
					        })
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const onSubmit = onSubmitOverridden
 | 
					    const onSubmit = onSubmitOverridden
 | 
				
			||||||
        ? onSubmitOverridden(formState.data, onFormChangeHandler, onSubmitHandler)
 | 
					        ? onSubmitOverridden(
 | 
				
			||||||
 | 
					              formState.data,
 | 
				
			||||||
 | 
					              onFormChangeHandler,
 | 
				
			||||||
 | 
					              onSubmitHandler
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
        : onSubmitHandler;
 | 
					        : onSubmitHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function getMessageOnLeavingPage() {
 | 
					    function getMessageOnLeavingPage() {
 | 
				
			||||||
        if (JSON.stringify(formState.data) === JSON.stringify(formState.initialData)) return true;
 | 
					        if (
 | 
				
			||||||
        return _("Changes you made may not be saved. Are you sure you want to leave?");
 | 
					            JSON.stringify(formState.data) ===
 | 
				
			||||||
 | 
					            JSON.stringify(formState.initialData)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        return _(
 | 
				
			||||||
 | 
					            "Changes you made may not be saved. Are you sure you want to leave?"
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,11 @@
 | 
				
			|||||||
`<ForisForm/>` is Higher-Order Component which encapsulates entire form logic and provides with children required props.
 | 
					`<ForisForm/>` is Higher-Order Component which encapsulates entire form logic
 | 
				
			||||||
This component structure provides comfort API and allows to create typical Foris module forms easily.
 | 
					and provides with children required props. This component structure provides
 | 
				
			||||||
 | 
					comfort API and allows to create typical Foris module forms easily.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Example of usage of `<ForisForm/>`
 | 
					## Example of usage of `<ForisForm/>`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You can pass more forms as children.
 | 
					You can pass more forms as children.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
<ForisForm
 | 
					<ForisForm
 | 
				
			||||||
    ws={ws}
 | 
					    ws={ws}
 | 
				
			||||||
@@ -24,7 +27,10 @@ You can pass more forms as children.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
export default function MACForm({
 | 
					export default function MACForm({
 | 
				
			||||||
    formData, formErrors, setFormValue, ...props
 | 
					    formData,
 | 
				
			||||||
 | 
					    formErrors,
 | 
				
			||||||
 | 
					    setFormValue,
 | 
				
			||||||
 | 
					    ...props
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
    const macSettings = formData.mac_settings;
 | 
					    const macSettings = formData.mac_settings;
 | 
				
			||||||
    const errors = (formErrors || {}).mac_settings || {};
 | 
					    const errors = (formErrors || {}).mac_settings || {};
 | 
				
			||||||
@@ -35,38 +41,33 @@ export default function MACForm({
 | 
				
			|||||||
                label={_("Custom MAC address")}
 | 
					                label={_("Custom MAC address")}
 | 
				
			||||||
                checked={macSettings.custom_mac_enabled}
 | 
					                checked={macSettings.custom_mac_enabled}
 | 
				
			||||||
                helpText={HELP_TEXTS.custom_mac_enabled}
 | 
					                helpText={HELP_TEXTS.custom_mac_enabled}
 | 
				
			||||||
 | 
					                onChange={setFormValue((value) => ({
 | 
				
			||||||
                onChange={setFormValue(
 | 
					                    mac_settings: { custom_mac_enabled: { $set: value } },
 | 
				
			||||||
                    (value) => ({ mac_settings: { custom_mac_enabled: { $set: value } } }),
 | 
					                }))}
 | 
				
			||||||
                )}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                {...props}
 | 
					                {...props}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
            {macSettings.custom_mac_enabled
 | 
					            {macSettings.custom_mac_enabled ? (
 | 
				
			||||||
                ? (
 | 
					 | 
				
			||||||
                <TextInput
 | 
					                <TextInput
 | 
				
			||||||
                    label={_("MAC address")}
 | 
					                    label={_("MAC address")}
 | 
				
			||||||
                    value={macSettings.custom_mac || ""}
 | 
					                    value={macSettings.custom_mac || ""}
 | 
				
			||||||
                    helpText={HELP_TEXTS.custom_mac}
 | 
					                    helpText={HELP_TEXTS.custom_mac}
 | 
				
			||||||
                    error={errors.custom_mac}
 | 
					                    error={errors.custom_mac}
 | 
				
			||||||
                    required
 | 
					                    required
 | 
				
			||||||
 | 
					                    onChange={setFormValue((value) => ({
 | 
				
			||||||
                        onChange={setFormValue(
 | 
					                        mac_settings: { custom_mac: { $set: value } },
 | 
				
			||||||
                            (value) => ({ mac_settings: { custom_mac: { $set: value } } }),
 | 
					                    }))}
 | 
				
			||||||
                        )}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    {...props}
 | 
					                    {...props}
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                )
 | 
					            ) : null}
 | 
				
			||||||
                : null}
 | 
					 | 
				
			||||||
        </>
 | 
					        </>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The <ForisForm/> passes subsequent `props` to the child components.
 | 
					The <ForisForm/> passes subsequent `props` to the child components.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| Prop           | Type   | Description                                                                |
 | 
					| Prop           | Type   | Description                                                                |
 | 
				
			||||||
|----------------|--------|----------------------------------------------------------------------------|
 | 
					| -------------- | ------ | -------------------------------------------------------------------------- |
 | 
				
			||||||
| `formData`     | object | Data returned from API.                                                    |
 | 
					| `formData`     | object | Data returned from API.                                                    |
 | 
				
			||||||
| `formErrors`   | object | Errors returned after validation via validator.                            |
 | 
					| `formErrors`   | object | Errors returned after validation via validator.                            |
 | 
				
			||||||
| `setFormValue` | func   | Function for data update. It takes update rule as arg (see example above). |
 | 
					| `setFormValue` | func   | Function for data update. It takes update rule as arg (see example above). |
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,8 +18,7 @@ export const STATES = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
SubmitButton.propTypes = {
 | 
					SubmitButton.propTypes = {
 | 
				
			||||||
    disabled: PropTypes.bool,
 | 
					    disabled: PropTypes.bool,
 | 
				
			||||||
    state: PropTypes.oneOf(Object.keys(STATES)
 | 
					    state: PropTypes.oneOf(Object.keys(STATES).map((key) => STATES[key])),
 | 
				
			||||||
        .map((key) => STATES[key])),
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function SubmitButton({ disabled, state, ...props }) {
 | 
					export function SubmitButton({ disabled, state, ...props }) {
 | 
				
			||||||
@@ -44,7 +43,6 @@ export function SubmitButton({ disabled, state, ...props }) {
 | 
				
			|||||||
            loading={loadingSubmitButton}
 | 
					            loading={loadingSubmitButton}
 | 
				
			||||||
            disabled={disableSubmitButton}
 | 
					            disabled={disableSubmitButton}
 | 
				
			||||||
            forisFormSize
 | 
					            forisFormSize
 | 
				
			||||||
 | 
					 | 
				
			||||||
            {...props}
 | 
					            {...props}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
            {labelSubmitButton}
 | 
					            {labelSubmitButton}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,29 +23,31 @@ export function useForm(validator, dataPreprocessor) {
 | 
				
			|||||||
        errors: {},
 | 
					        errors: {},
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const onFormReload = useCallback((data) => {
 | 
					    const onFormReload = useCallback(
 | 
				
			||||||
 | 
					        (data) => {
 | 
				
			||||||
            dispatch({
 | 
					            dispatch({
 | 
				
			||||||
                type: FORM_ACTIONS.resetData,
 | 
					                type: FORM_ACTIONS.resetData,
 | 
				
			||||||
                data,
 | 
					                data,
 | 
				
			||||||
                dataPreprocessor,
 | 
					                dataPreprocessor,
 | 
				
			||||||
                validator,
 | 
					                validator,
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
    }, [dataPreprocessor, validator]);
 | 
					        },
 | 
				
			||||||
 | 
					        [dataPreprocessor, validator]
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const onFormChangeHandler = useCallback((updateRule) => (event) => {
 | 
					    const onFormChangeHandler = useCallback(
 | 
				
			||||||
 | 
					        (updateRule) => (event) => {
 | 
				
			||||||
            dispatch({
 | 
					            dispatch({
 | 
				
			||||||
                type: FORM_ACTIONS.updateValue,
 | 
					                type: FORM_ACTIONS.updateValue,
 | 
				
			||||||
                value: getChangedValue(event.target),
 | 
					                value: getChangedValue(event.target),
 | 
				
			||||||
                updateRule,
 | 
					                updateRule,
 | 
				
			||||||
                validator,
 | 
					                validator,
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
    }, [validator]);
 | 
					        },
 | 
				
			||||||
 | 
					        [validator]
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return [
 | 
					    return [state, onFormChangeHandler, onFormReload];
 | 
				
			||||||
        state,
 | 
					 | 
				
			||||||
        onFormChangeHandler,
 | 
					 | 
				
			||||||
        onFormReload,
 | 
					 | 
				
			||||||
    ];
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function formReducer(state, action) {
 | 
					function formReducer(state, action) {
 | 
				
			||||||
@@ -64,7 +66,9 @@ function formReducer(state, action) {
 | 
				
			|||||||
                return { ...state, initialData: state.data };
 | 
					                return { ...state, initialData: state.data };
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const data = action.dataPreprocessor ? action.dataPreprocessor(action.data) : action.data;
 | 
					            const data = action.dataPreprocessor
 | 
				
			||||||
 | 
					                ? action.dataPreprocessor(action.data)
 | 
				
			||||||
 | 
					                : action.data;
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                data,
 | 
					                data,
 | 
				
			||||||
                initialData: data,
 | 
					                initialData: data,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										33
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								src/index.js
									
									
									
									
									
								
							@@ -30,18 +30,11 @@ export { PasswordInput } from "./bootstrap/PasswordInput";
 | 
				
			|||||||
export { Radio, RadioSet } from "./bootstrap/RadioSet";
 | 
					export { Radio, RadioSet } from "./bootstrap/RadioSet";
 | 
				
			||||||
export { Select } from "./bootstrap/Select";
 | 
					export { Select } from "./bootstrap/Select";
 | 
				
			||||||
export { TextInput } from "./bootstrap/TextInput";
 | 
					export { TextInput } from "./bootstrap/TextInput";
 | 
				
			||||||
export { formFieldsSize } from "./bootstrap/constants";
 | 
					export { formFieldsSize, buttonFormFieldsSize } from "./bootstrap/constants";
 | 
				
			||||||
 | 
					export { Switch } from "./bootstrap/Switch";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export {
 | 
					export { Spinner, SpinnerElement } from "./bootstrap/Spinner";
 | 
				
			||||||
    Spinner,
 | 
					export { Modal, ModalBody, ModalFooter, ModalHeader } from "./bootstrap/Modal";
 | 
				
			||||||
    SpinnerElement,
 | 
					 | 
				
			||||||
} from "./bootstrap/Spinner";
 | 
					 | 
				
			||||||
export {
 | 
					 | 
				
			||||||
    Modal,
 | 
					 | 
				
			||||||
    ModalBody,
 | 
					 | 
				
			||||||
    ModalFooter,
 | 
					 | 
				
			||||||
    ModalHeader,
 | 
					 | 
				
			||||||
} from "./bootstrap/Modal";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Common
 | 
					// Common
 | 
				
			||||||
export { RebootButton } from "./common/RebootButton";
 | 
					export { RebootButton } from "./common/RebootButton";
 | 
				
			||||||
@@ -49,7 +42,10 @@ export { WiFiSettings } from "./common/WiFiSettings/WiFiSettings";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Form
 | 
					// Form
 | 
				
			||||||
export { ForisForm } from "./form/components/ForisForm";
 | 
					export { ForisForm } from "./form/components/ForisForm";
 | 
				
			||||||
export { SubmitButton, STATES as SUBMIT_BUTTON_STATES } from "./form/components/SubmitButton";
 | 
					export {
 | 
				
			||||||
 | 
					    SubmitButton,
 | 
				
			||||||
 | 
					    STATES as SUBMIT_BUTTON_STATES,
 | 
				
			||||||
 | 
					} from "./form/components/SubmitButton";
 | 
				
			||||||
export { useForisModule, useForm } from "./form/hooks";
 | 
					export { useForisModule, useForm } from "./form/hooks";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// WebSockets
 | 
					// WebSockets
 | 
				
			||||||
@@ -58,9 +54,18 @@ export { WebSockets } from "./webSockets/WebSockets";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Utils
 | 
					// Utils
 | 
				
			||||||
export { Portal } from "./utils/Portal";
 | 
					export { Portal } from "./utils/Portal";
 | 
				
			||||||
export { undefinedIfEmpty, withoutUndefinedKeys, onlySpecifiedKeys } from "./utils/objectHelpers";
 | 
					 | 
				
			||||||
export {
 | 
					export {
 | 
				
			||||||
    withEither, withSpinner, withSending, withSpinnerOnSending, withError, withErrorMessage,
 | 
					    undefinedIfEmpty,
 | 
				
			||||||
 | 
					    withoutUndefinedKeys,
 | 
				
			||||||
 | 
					    onlySpecifiedKeys,
 | 
				
			||||||
 | 
					} from "./utils/objectHelpers";
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					    withEither,
 | 
				
			||||||
 | 
					    withSpinner,
 | 
				
			||||||
 | 
					    withSending,
 | 
				
			||||||
 | 
					    withSpinnerOnSending,
 | 
				
			||||||
 | 
					    withError,
 | 
				
			||||||
 | 
					    withErrorMessage,
 | 
				
			||||||
} from "./utils/conditionalHOCs";
 | 
					} from "./utils/conditionalHOCs";
 | 
				
			||||||
export { ErrorMessage } from "./utils/ErrorMessage";
 | 
					export { ErrorMessage } from "./utils/ErrorMessage";
 | 
				
			||||||
export { useClickOutside } from "./utils/hooks";
 | 
					export { useClickOutside } from "./utils/hooks";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@ window.AlertContext = React.createContext();
 | 
				
			|||||||
function AlertContextMock({ children }) {
 | 
					function AlertContextMock({ children }) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <AlertContext.Provider value={[mockSetAlert, mockDismissAlert]}>
 | 
					        <AlertContext.Provider value={[mockSetAlert, mockDismissAlert]}>
 | 
				
			||||||
            { children }
 | 
					            {children}
 | 
				
			||||||
        </AlertContext.Provider>
 | 
					        </AlertContext.Provider>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,15 +26,14 @@ function Wrapper({ children }) {
 | 
				
			|||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <AlertContextMock>
 | 
					        <AlertContextMock>
 | 
				
			||||||
            <StaticRouter>
 | 
					            <StaticRouter>
 | 
				
			||||||
                <UIDReset>
 | 
					                <UIDReset>{children}</UIDReset>
 | 
				
			||||||
                    {children}
 | 
					 | 
				
			||||||
                </UIDReset>
 | 
					 | 
				
			||||||
            </StaticRouter>
 | 
					            </StaticRouter>
 | 
				
			||||||
        </AlertContextMock>
 | 
					        </AlertContextMock>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const customTestRender = (ui, options) => render(ui, { wrapper: Wrapper, ...options });
 | 
					const customTestRender = (ui, options) =>
 | 
				
			||||||
 | 
					    render(ui, { wrapper: Wrapper, ...options });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// re-export everything
 | 
					// re-export everything
 | 
				
			||||||
export * from "@testing-library/react";
 | 
					export * from "@testing-library/react";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,5 +8,7 @@
 | 
				
			|||||||
import mockAxios from "jest-mock-axios";
 | 
					import mockAxios from "jest-mock-axios";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function mockJSONError(data) {
 | 
					export function mockJSONError(data) {
 | 
				
			||||||
    mockAxios.mockError({ response: { data, headers: { "content-type": "application/json" } } });
 | 
					    mockAxios.mockError({
 | 
				
			||||||
 | 
					        response: { data, headers: { "content-type": "application/json" } },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,5 @@ ErrorMessage.defaultProps = {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function ErrorMessage({ message }) {
 | 
					export function ErrorMessage({ message }) {
 | 
				
			||||||
    return (
 | 
					    return <p className="text-center text-danger">{message}</p>;
 | 
				
			||||||
        <p className="text-center text-danger">{message}</p>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,12 @@ import React from "react";
 | 
				
			|||||||
import { render } from "customTestRender";
 | 
					import { render } from "customTestRender";
 | 
				
			||||||
import { API_STATE } from "api/utils";
 | 
					import { API_STATE } from "api/utils";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    withEither, withSpinner, withSending, withSpinnerOnSending, withError, withErrorMessage,
 | 
					    withEither,
 | 
				
			||||||
 | 
					    withSpinner,
 | 
				
			||||||
 | 
					    withSending,
 | 
				
			||||||
 | 
					    withSpinnerOnSending,
 | 
				
			||||||
 | 
					    withError,
 | 
				
			||||||
 | 
					    withErrorMessage,
 | 
				
			||||||
} from "../conditionalHOCs";
 | 
					} from "../conditionalHOCs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("conditional HOCs", () => {
 | 
					describe("conditional HOCs", () => {
 | 
				
			||||||
@@ -52,14 +57,18 @@ describe("conditional HOCs", () => {
 | 
				
			|||||||
        it("should render First component", () => {
 | 
					        it("should render First component", () => {
 | 
				
			||||||
            const withAlternative = withSending(Alternative);
 | 
					            const withAlternative = withSending(Alternative);
 | 
				
			||||||
            const FirstWithConditional = withAlternative(First);
 | 
					            const FirstWithConditional = withAlternative(First);
 | 
				
			||||||
            const { getByText } = render(<FirstWithConditional apiState={API_STATE.SUCCESS} />);
 | 
					            const { getByText } = render(
 | 
				
			||||||
 | 
					                <FirstWithConditional apiState={API_STATE.SUCCESS} />
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
            expect(getByText("First")).toBeDefined();
 | 
					            expect(getByText("First")).toBeDefined();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it("should render Alternative component", () => {
 | 
					        it("should render Alternative component", () => {
 | 
				
			||||||
            const withAlternative = withSending(Alternative);
 | 
					            const withAlternative = withSending(Alternative);
 | 
				
			||||||
            const FirstWithConditional = withAlternative(First);
 | 
					            const FirstWithConditional = withAlternative(First);
 | 
				
			||||||
            const { getByText } = render(<FirstWithConditional apiState={API_STATE.SENDING} />);
 | 
					            const { getByText } = render(
 | 
				
			||||||
 | 
					                <FirstWithConditional apiState={API_STATE.SENDING} />
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
            expect(getByText("Alternative")).toBeDefined();
 | 
					            expect(getByText("Alternative")).toBeDefined();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -67,13 +76,17 @@ describe("conditional HOCs", () => {
 | 
				
			|||||||
    describe("withSpinnerOnSending", () => {
 | 
					    describe("withSpinnerOnSending", () => {
 | 
				
			||||||
        it("should render First component", () => {
 | 
					        it("should render First component", () => {
 | 
				
			||||||
            const FirstWithConditional = withSpinnerOnSending(First);
 | 
					            const FirstWithConditional = withSpinnerOnSending(First);
 | 
				
			||||||
            const { getByText } = render(<FirstWithConditional apiState={API_STATE.SUCCESS} />);
 | 
					            const { getByText } = render(
 | 
				
			||||||
 | 
					                <FirstWithConditional apiState={API_STATE.SUCCESS} />
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
            expect(getByText("First")).toBeDefined();
 | 
					            expect(getByText("First")).toBeDefined();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it("should render spinner", () => {
 | 
					        it("should render spinner", () => {
 | 
				
			||||||
            const FirstWithConditional = withSpinnerOnSending(First);
 | 
					            const FirstWithConditional = withSpinnerOnSending(First);
 | 
				
			||||||
            const { container } = render(<FirstWithConditional apiState={API_STATE.SENDING} />);
 | 
					            const { container } = render(
 | 
				
			||||||
 | 
					                <FirstWithConditional apiState={API_STATE.SENDING} />
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
            expect(container).toMatchSnapshot();
 | 
					            expect(container).toMatchSnapshot();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -97,13 +110,17 @@ describe("conditional HOCs", () => {
 | 
				
			|||||||
    describe("withErrorMessage", () => {
 | 
					    describe("withErrorMessage", () => {
 | 
				
			||||||
        it("should render First component", () => {
 | 
					        it("should render First component", () => {
 | 
				
			||||||
            const FirstWithConditional = withErrorMessage(First);
 | 
					            const FirstWithConditional = withErrorMessage(First);
 | 
				
			||||||
            const { getByText } = render(<FirstWithConditional apiState={API_STATE.SUCCESS} />);
 | 
					            const { getByText } = render(
 | 
				
			||||||
 | 
					                <FirstWithConditional apiState={API_STATE.SUCCESS} />
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
            expect(getByText("First")).toBeDefined();
 | 
					            expect(getByText("First")).toBeDefined();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it("should render error message", () => {
 | 
					        it("should render error message", () => {
 | 
				
			||||||
            const FirstWithConditional = withErrorMessage(First);
 | 
					            const FirstWithConditional = withErrorMessage(First);
 | 
				
			||||||
            const { container } = render(<FirstWithConditional apiState={API_STATE.ERROR} />);
 | 
					            const { container } = render(
 | 
				
			||||||
 | 
					                <FirstWithConditional apiState={API_STATE.ERROR} />
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
            expect(container).toMatchSnapshot();
 | 
					            expect(container).toMatchSnapshot();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,42 +10,40 @@ import { toLocaleDateString } from "../datetime";
 | 
				
			|||||||
describe("toLocaleDateString", () => {
 | 
					describe("toLocaleDateString", () => {
 | 
				
			||||||
    it("should work with different locale", () => {
 | 
					    it("should work with different locale", () => {
 | 
				
			||||||
        global.ForisTranslations = { locale: "fr" };
 | 
					        global.ForisTranslations = { locale: "fr" };
 | 
				
			||||||
        expect(
 | 
					        expect(toLocaleDateString("2020-02-20T12:51:36+00:00")).toBe(
 | 
				
			||||||
            toLocaleDateString("2020-02-20T12:51:36+00:00")
 | 
					            "20 février 2020 12:51"
 | 
				
			||||||
        ).toBe("20 février 2020 12:51");
 | 
					        );
 | 
				
			||||||
        global.ForisTranslations = { locale: "en" };
 | 
					        global.ForisTranslations = { locale: "en" };
 | 
				
			||||||
    })
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("should convert with default format", () => {
 | 
					    it("should convert with default format", () => {
 | 
				
			||||||
        expect(
 | 
					        expect(toLocaleDateString("2020-02-20T12:51:36+00:00")).toBe(
 | 
				
			||||||
            toLocaleDateString("2020-02-20T12:51:36+00:00")
 | 
					            "February 20, 2020 12:51 PM"
 | 
				
			||||||
        ).toBe("February 20, 2020 12:51 PM");
 | 
					        );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("should convert with custom input format", () => {
 | 
					    it("should convert with custom input format", () => {
 | 
				
			||||||
        expect(
 | 
					        expect(
 | 
				
			||||||
            toLocaleDateString(
 | 
					            toLocaleDateString("2020-02-20 12:51:36 +0000", {
 | 
				
			||||||
                "2020-02-20 12:51:36 +0000",
 | 
					                inputFormat: "YYYY-MM-DD HH:mm:ss Z",
 | 
				
			||||||
                { inputFormat: "YYYY-MM-DD HH:mm:ss Z" },
 | 
					            })
 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        ).toBe("February 20, 2020 12:51 PM");
 | 
					        ).toBe("February 20, 2020 12:51 PM");
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("should convert with custom output format", () => {
 | 
					    it("should convert with custom output format", () => {
 | 
				
			||||||
        expect(
 | 
					        expect(
 | 
				
			||||||
            toLocaleDateString(
 | 
					            toLocaleDateString("2020-02-20T12:51:36+00:00", {
 | 
				
			||||||
                "2020-02-20T12:51:36+00:00",
 | 
					                outputFormat: "LL",
 | 
				
			||||||
                { outputFormat: "LL" },
 | 
					            })
 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        ).toBe("February 20, 2020");
 | 
					        ).toBe("February 20, 2020");
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("should convert with custom input and output format", () => {
 | 
					    it("should convert with custom input and output format", () => {
 | 
				
			||||||
        expect(
 | 
					        expect(
 | 
				
			||||||
            toLocaleDateString(
 | 
					            toLocaleDateString("2020-02-20 12:51:36 +0000", {
 | 
				
			||||||
                "2020-02-20 12:51:36 +0000",
 | 
					                inputFormat: "YYYY-MM-DD HH:mm:ss Z",
 | 
				
			||||||
                { inputFormat: "YYYY-MM-DD HH:mm:ss Z", outputFormat: "LL" },
 | 
					                outputFormat: "LL",
 | 
				
			||||||
            )
 | 
					            })
 | 
				
			||||||
        ).toBe("February 20, 2020");
 | 
					        ).toBe("February 20, 2020");
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,8 +24,8 @@ function withEither(conditionalFn, Either) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function isSending(props) {
 | 
					function isSending(props) {
 | 
				
			||||||
    if (Array.isArray(props.apiState)) {
 | 
					    if (Array.isArray(props.apiState)) {
 | 
				
			||||||
        return props.apiState.some(
 | 
					        return props.apiState.some((state) =>
 | 
				
			||||||
            (state) => [API_STATE.INIT, API_STATE.SENDING].includes(state),
 | 
					            [API_STATE.INIT, API_STATE.SENDING].includes(state)
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return [API_STATE.INIT, API_STATE.SENDING].includes(props.apiState);
 | 
					    return [API_STATE.INIT, API_STATE.SENDING].includes(props.apiState);
 | 
				
			||||||
@@ -38,15 +38,18 @@ const withSpinnerOnSending = withSpinner(isSending);
 | 
				
			|||||||
// Error handling
 | 
					// Error handling
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const withError = (conditionalFn) => withEither(conditionalFn, ErrorMessage);
 | 
					const withError = (conditionalFn) => withEither(conditionalFn, ErrorMessage);
 | 
				
			||||||
const withErrorMessage = withError(
 | 
					const withErrorMessage = withError((props) => {
 | 
				
			||||||
    (props) => {
 | 
					 | 
				
			||||||
    if (Array.isArray(props.apiState)) {
 | 
					    if (Array.isArray(props.apiState)) {
 | 
				
			||||||
        return props.apiState.includes(API_STATE.ERROR);
 | 
					        return props.apiState.includes(API_STATE.ERROR);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return props.apiState === API_STATE.ERROR;
 | 
					    return props.apiState === API_STATE.ERROR;
 | 
				
			||||||
    },
 | 
					});
 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export {
 | 
					export {
 | 
				
			||||||
    withEither, withSpinner, withSending, withSpinnerOnSending, withError, withErrorMessage,
 | 
					    withEither,
 | 
				
			||||||
 | 
					    withSpinner,
 | 
				
			||||||
 | 
					    withSending,
 | 
				
			||||||
 | 
					    withSpinnerOnSending,
 | 
				
			||||||
 | 
					    withError,
 | 
				
			||||||
 | 
					    withErrorMessage,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,9 @@
 | 
				
			|||||||
import moment from "moment";
 | 
					import moment from "moment";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function toLocaleDateString(date, { inputFormat, outputFormat = "LLL" } = {}) {
 | 
					export function toLocaleDateString(
 | 
				
			||||||
 | 
					    date,
 | 
				
			||||||
 | 
					    { inputFormat, outputFormat = "LLL" } = {}
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
    const parsedDate = inputFormat ? moment(date, inputFormat) : moment(date);
 | 
					    const parsedDate = inputFormat ? moment(date, inputFormat) : moment(date);
 | 
				
			||||||
    return parsedDate
 | 
					    return parsedDate.locale(ForisTranslations.locale).format(outputFormat);
 | 
				
			||||||
        .locale(ForisTranslations.locale)
 | 
					 | 
				
			||||||
        .format(outputFormat);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,9 +21,13 @@ export const ForisURLs = {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Notifications links are used with <Link/> inside Router, thus url subdir is not required.
 | 
					    // Notifications links are used with <Link/> inside Router, thus url subdir is not required.
 | 
				
			||||||
    notifications: "/notifications",
 | 
					    overview: "/overview",
 | 
				
			||||||
 | 
					    notifications: "/overview#notifications",
 | 
				
			||||||
    notificationsSettings: "/administration/notifications-settings",
 | 
					    notificationsSettings: "/administration/notifications-settings",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    approveUpdates: "/package-management/updates",
 | 
				
			||||||
 | 
					    languages: "/package-management/languages",
 | 
				
			||||||
 | 
					    rebootPage: "/reforis/administration/reboot",
 | 
				
			||||||
    luci: "/cgi-bin/luci",
 | 
					    luci: "/cgi-bin/luci",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // API
 | 
					    // API
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,11 +8,17 @@
 | 
				
			|||||||
import { useState, useEffect } from "react";
 | 
					import { useState, useEffect } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Execute callback when condition is set to true. */
 | 
					/** Execute callback when condition is set to true. */
 | 
				
			||||||
export function useConditionalTimeout({ callback, timeout = 125 }, ...callbackArgs) {
 | 
					export function useConditionalTimeout(
 | 
				
			||||||
 | 
					    { callback, timeout = 125 },
 | 
				
			||||||
 | 
					    ...callbackArgs
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
    const [condition, setCondition] = useState(false);
 | 
					    const [condition, setCondition] = useState(false);
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
        if (condition) {
 | 
					        if (condition) {
 | 
				
			||||||
            const interval = setTimeout(() => callback(...callbackArgs), timeout);
 | 
					            const interval = setTimeout(
 | 
				
			||||||
 | 
					                () => callback(...callbackArgs),
 | 
				
			||||||
 | 
					                timeout
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
            return () => setTimeout(interval);
 | 
					            return () => setTimeout(interval);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }, [condition, callback, timeout, callbackArgs]);
 | 
					    }, [condition, callback, timeout, callbackArgs]);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,21 +15,18 @@ export function undefinedIfEmpty(instance) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/** Return object without keys that have undefined value. */
 | 
					/** Return object without keys that have undefined value. */
 | 
				
			||||||
export function withoutUndefinedKeys(instance) {
 | 
					export function withoutUndefinedKeys(instance) {
 | 
				
			||||||
    return Object.keys(instance).reduce(
 | 
					    return Object.keys(instance).reduce((accumulator, key) => {
 | 
				
			||||||
        (accumulator, key) => {
 | 
					 | 
				
			||||||
        if (instance[key] !== undefined) {
 | 
					        if (instance[key] !== undefined) {
 | 
				
			||||||
            accumulator[key] = instance[key];
 | 
					            accumulator[key] = instance[key];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return accumulator;
 | 
					        return accumulator;
 | 
				
			||||||
        },
 | 
					    }, {});
 | 
				
			||||||
        {},
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Return copy of passed object that has only desired keys. */
 | 
					/** Return copy of passed object that has only desired keys. */
 | 
				
			||||||
export function onlySpecifiedKeys(object, desiredKeys) {
 | 
					export function onlySpecifiedKeys(object, desiredKeys) {
 | 
				
			||||||
    return desiredKeys.reduce(
 | 
					    return desiredKeys.reduce((accumulator, key) => {
 | 
				
			||||||
        (accumulator, key) => { accumulator[key] = object[key]; return accumulator; },
 | 
					        accumulator[key] = object[key];
 | 
				
			||||||
        {},
 | 
					        return accumulator;
 | 
				
			||||||
    );
 | 
					    }, {});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,7 +30,10 @@ const REs = {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const createValidator = (fieldType) => (value) => {
 | 
					const createValidator = (fieldType) => (value) => {
 | 
				
			||||||
    if (value && value !== "") return REs[fieldType].test(value) ? undefined : ERROR_MESSAGES[fieldType];
 | 
					    if (value && value !== "")
 | 
				
			||||||
 | 
					        return REs[fieldType].test(value)
 | 
				
			||||||
 | 
					            ? undefined
 | 
				
			||||||
 | 
					            : ERROR_MESSAGES[fieldType];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const validateIPv4Address = createValidator("IPv4");
 | 
					const validateIPv4Address = createValidator("IPv4");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,7 +22,9 @@ export class WebSockets {
 | 
				
			|||||||
        this.ws = new WebSocket(URL);
 | 
					        this.ws = new WebSocket(URL);
 | 
				
			||||||
        this.ws.onerror = (e) => {
 | 
					        this.ws.onerror = (e) => {
 | 
				
			||||||
            if (window.location.pathname !== ForisURLs.login) {
 | 
					            if (window.location.pathname !== ForisURLs.login) {
 | 
				
			||||||
                console.error("WS: Error observed, you aren't logged probably.");
 | 
					                console.error(
 | 
				
			||||||
 | 
					                    "WS: Error observed, you aren't logged probably."
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
                window.location.replace(ForisURLs.login);
 | 
					                window.location.replace(ForisURLs.login);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            console.error(`WS: Error: ${e}`);
 | 
					            console.error(`WS: Error: ${e}`);
 | 
				
			||||||
@@ -111,7 +113,9 @@ export class WebSockets {
 | 
				
			|||||||
            chain = this.callbacks[json.module][json.action];
 | 
					            chain = this.callbacks[json.module][json.action];
 | 
				
			||||||
        } catch (error) {
 | 
					        } catch (error) {
 | 
				
			||||||
            if (error instanceof TypeError) {
 | 
					            if (error instanceof TypeError) {
 | 
				
			||||||
                console.warn(`Callback for this message wasn't found:${error.data}`);
 | 
					                console.warn(
 | 
				
			||||||
 | 
					                    `Callback for this message wasn't found:${error.data}`
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
            } else throw error;
 | 
					            } else throw error;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,12 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { useEffect, useState } from "react";
 | 
					import { useEffect, useState } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function useWSForisModule(ws, module, action = "update_settings", controllerID) {
 | 
					export function useWSForisModule(
 | 
				
			||||||
 | 
					    ws,
 | 
				
			||||||
 | 
					    module,
 | 
				
			||||||
 | 
					    action = "update_settings",
 | 
				
			||||||
 | 
					    controllerID
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
    const [data, setData] = useState(null);
 | 
					    const [data, setData] = useState(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
@@ -18,14 +23,16 @@ export function useWSForisModule(ws, module, action = "update_settings", control
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        function callback(message) {
 | 
					        function callback(message) {
 | 
				
			||||||
            // Accept only messages addressed to device with passed controller ID.
 | 
					            // Accept only messages addressed to device with passed controller ID.
 | 
				
			||||||
            if (controllerID !== undefined && controllerID !== message.controller_id) {
 | 
					            if (
 | 
				
			||||||
 | 
					                controllerID !== undefined &&
 | 
				
			||||||
 | 
					                controllerID !== message.controller_id
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            setData(message.data);
 | 
					            setData(message.data);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ws.subscribe(module)
 | 
					        ws.subscribe(module).bind(module, action, callback);
 | 
				
			||||||
            .bind(module, action, callback);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return () => {
 | 
					        return () => {
 | 
				
			||||||
            ws.unbind(module, action, callback);
 | 
					            ws.unbind(module, action, callback);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,9 +30,7 @@ module.exports = {
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            name: "Alert Context",
 | 
					            name: "Alert Context",
 | 
				
			||||||
            components: [
 | 
					            components: ["src/alertContext/AlertContext.js"],
 | 
				
			||||||
                "src/alertContext/AlertContext.js",
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
            exampleMode: "expand",
 | 
					            exampleMode: "expand",
 | 
				
			||||||
            usageMode: "expand",
 | 
					            usageMode: "expand",
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
@@ -42,16 +40,20 @@ module.exports = {
 | 
				
			|||||||
            components: "src/bootstrap/*.js",
 | 
					            components: "src/bootstrap/*.js",
 | 
				
			||||||
            exampleMode: "expand",
 | 
					            exampleMode: "expand",
 | 
				
			||||||
            usageMode: "expand",
 | 
					            usageMode: "expand",
 | 
				
			||||||
            ignore: [
 | 
					            ignore: ["src/bootstrap/constants.js"],
 | 
				
			||||||
                "src/bootstrap/constants.js",
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    require: [
 | 
					    require: [
 | 
				
			||||||
        "babel-polyfill",
 | 
					        "babel-polyfill",
 | 
				
			||||||
        path.join(__dirname, "src/testUtils/mockGlobals"),
 | 
					        path.join(__dirname, "src/testUtils/mockGlobals"),
 | 
				
			||||||
        path.join(__dirname, "node_modules/bootstrap/dist/css/bootstrap.min.css"),
 | 
					        path.join(
 | 
				
			||||||
        path.join(__dirname, "node_modules/@fortawesome/fontawesome-free/css/all.min.css"),
 | 
					            __dirname,
 | 
				
			||||||
 | 
					            "node_modules/bootstrap/dist/css/bootstrap.min.css"
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        path.join(
 | 
				
			||||||
 | 
					            __dirname,
 | 
				
			||||||
 | 
					            "node_modules/@fortawesome/fontawesome-free/css/all.min.css"
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    webpackConfig: {
 | 
					    webpackConfig: {
 | 
				
			||||||
        module: {
 | 
					        module: {
 | 
				
			||||||
@@ -60,10 +62,12 @@ module.exports = {
 | 
				
			|||||||
                    test: /\.js$/,
 | 
					                    test: /\.js$/,
 | 
				
			||||||
                    exclude: /node_modules/,
 | 
					                    exclude: /node_modules/,
 | 
				
			||||||
                    loader: "babel-loader",
 | 
					                    loader: "babel-loader",
 | 
				
			||||||
                }, {
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
                    test: /\.css$/,
 | 
					                    test: /\.css$/,
 | 
				
			||||||
                    use: ["style-loader", "css-loader"],
 | 
					                    use: ["style-loader", "css-loader"],
 | 
				
			||||||
                }, {
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
                    test: /\.(jpg|jpeg|png|woff|woff2|eot|ttf|svg)$/,
 | 
					                    test: /\.(jpg|jpeg|png|woff|woff2|eot|ttf|svg)$/,
 | 
				
			||||||
                    loader: "file-loader",
 | 
					                    loader: "file-loader",
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user