diff --git a/.babelrc.esm-build b/.babelrc.esm-build new file mode 100644 index 000000000..413b47297 --- /dev/null +++ b/.babelrc.esm-build @@ -0,0 +1,29 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "modules": false + } + ], + "@babel/react" + ], + "plugins": [ + [ + "formatjs", + { + "idInterpolationPattern": "[sha512:contenthash:base64:6]", + "ast": true + } + ], + [ + "module-resolver", + { + "root": ["./src"], + "alias": { + "": "./src" + } + } + ] + ] +} diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 954b85c6e..47b6024bf 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,7 +1,7 @@ [bumpversion] commit = False tag = False -current_version = 1.1.0-rc.0 +current_version = 1.2.0-alpha.1 parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P
[a-z]+)\.(?P\d+))?
 serialize = 
 	{major}.{minor}.{patch}-{pre}.{build}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a997a6451..f3d1f5661 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,29 +15,89 @@ env:
   IMAGE_NAME: ${{ secrets.OTA_ACR_URL }}/openformulieren/open-forms-sdk
 
 jobs:
-  tests:
-    name: Run Javascript tests
+  build:
+    name: Create 'production' build
     runs-on: ubuntu-latest
 
     steps:
       - uses: actions/checkout@v2
       - uses: actions/setup-node@v3
         with:
-          node-version: '16'
+          node-version-file: '.nvmrc'
 
       - name: Build Javascript
         run: |
           yarn install
           yarn build
 
+      - name: Store build artifact
+        uses: actions/upload-artifact@v2
+        with:
+          name: sdk-build
+          path: dist/
+          retention-days: 1
+
+  tests:
+    name: Run Javascript tests
+    runs-on: ubuntu-latest
+    needs: build
+
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/setup-node@v3
+        with:
+          node-version-file: '.nvmrc'
+
+      - name: Install dependencies
+        run: yarn install
+
+      - name: Download build artifact
+        uses: actions/download-artifact@v2
+        with:
+          name: sdk-build
+
       - name: Run tests
         run: |
           yarn test
         env:
           CI: 'true'
 
-      # - name: Publish coverage report
-      #   uses: codecov/codecov-action@v1
+  publish:
+    name: Publish the NPM package
+    runs-on: ubuntu-latest
+    needs:
+      - build
+      - tests
+
+    if: startsWith(github.ref, 'refs/tags/')
+
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/setup-node@v3
+        with:
+          node-version-file: '.nvmrc'
+          registry-url: 'https://registry.npmjs.org'
+          scope: '@open-formulieren'
+
+      - name: Install dependencies
+        run: yarn
+
+      - name: Download build artifact
+        uses: actions/download-artifact@v2
+        with:
+          name: sdk-build
+
+      - name: Publish package to NPM
+        run: |
+          mv README.npm.md README.md
+          yarn prepare-package
+
+          # Strip git ref prefix from version
+          VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
+
+          yarn publish --access public --new-version=$VERSION
+        env:
+          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
 
   docker:
     name: Build (and push) Docker image
diff --git a/.gitignore b/.gitignore
index d75a4171f..2b245d667 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
 .vscode/
 /env/
 .idea/
+/venv/
 
 # dependencies
 /node_modules
@@ -16,6 +17,9 @@
 # production
 /build
 /dist
+/lib
+/esm
+*.tgz
 
 # misc
 .DS_Store
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 0966c00ca..cc8fcd705 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -2,6 +2,28 @@
 Changelog
 =========
 
+1.1.1 (2022-07-25)
+==================
+
+Fixed a number of bugs
+
+* [#1526] Fixed a situation where users could get "stuck" on a form step - backend logic
+  checks are now always performed, using the input data that validates client-side.
+* [#1687] Fixed the SDK progressing to the next step even if the backend has validation
+  errors on step submission.
+* Fixed displaying (generic) backend errors in a user-friendly way
+
+1.0.4 (2022-07-25)
+==================
+
+Fixed a number of bugs
+
+* [#1526] Fixed a situation where users could get "stuck" on a form step - backend logic
+  checks are now always performed, using the input data that validates client-side.
+* [#1687] Fixed the SDK progressing to the next step even if the backend has validation
+  errors on step submission.
+* Fixed displaying (generic) backend errors in a user-friendly way
+
 1.1.0 (2022-05-24)
 ==================
 
diff --git a/README.NL.rst b/README.NL.rst
index 66b443026..c2c134007 100644
--- a/README.NL.rst
+++ b/README.NL.rst
@@ -2,8 +2,8 @@
 Open Formulieren SDK
 ====================
 
-:Version: 1.1.0-rc.0
-:Source: https://github.com/maykinmedia/open-forms-sdk
+:Version: 1.2.0-alpha.1
+:Source: https://github.com/open-formulieren/open-forms-sdk
 :Keywords: e-Formulieren, Common Ground, FormIO, API
 
 |docs|
diff --git a/README.npm.md b/README.npm.md
new file mode 100644
index 000000000..08809d17f
--- /dev/null
+++ b/README.npm.md
@@ -0,0 +1,91 @@
+# Open Forms SDK
+
+[![NPM package](https://img.shields.io/npm/v/@open-formulieren/sdk.svg)](https://www.npmjs.com/package/@open-formulieren/sdk)
+
+The Open Forms SDK is the frontend to the [Open Forms backend][backend]. We publish it
+as both an NPM library of components and pre-built [Docker image][docker].
+
+The documentation is available [online][docs]. Note that the SDK documentation is
+currently lacking. We're planning to publish Storybook documentation soon.
+
+## Audience
+
+The target audience for this library is developers who want to compose their own version
+of the SDK without forking the repository.
+
+Open Forms is developed as a white-label application, including the SDK. While the
+UI components are fairly generic, there are more organization/theme-specific layouts
+possible, requiring significant markup changes.
+
+Rather than exposing and maintaining complex options for UI-component
+customization, we decided to publish the SDK as a library so that (experienced)
+developers can replace components as they need.
+
+## Usage
+
+The package exports two ways to use the library:
+
+1. Importing and composing the invididual modules (ESM)
+2. Importing the library as a whole
+
+The former approach allows more fine-grained control and should exclude code/dependencies
+that aren't used and result in smaller builds, while the latter gives you the public API
+as it would be available in the browser.
+
+Given the target audience, we expect developers to use option 1.
+
+### Using individual modules
+
+The underlying React components are published and can serve as a basis for your own
+components, or even completely replace them (TODO - this is on the roadmap!).
+
+E.g. to re-use the card component:
+
+```jsx
+import Card from '@openformulieren/sdk/components/Card';
+
+const MyCard = (props) => ();
+
+export default MyCard;
+```
+
+**NOTE**: the published components are semi-private API. We cannot provide guarantees
+that it will be 100% backwards-compatible. If we know breaking changes are made, we will
+bump the major version number, but you should probably do extensive testing even with
+minor versions.
+
+**Exposed API**
+
+The `package.json` describes the module exports, at the time of writing these are:
+
+* `@openformulieren/sdk/components/*`
+* `@openformulieren/sdk/hooks/*`
+* `@openformulieren/sdk/types/*`
+* `@openformulieren/sdk/map/*`
+* `@openformulieren/sdk/sdk`
+* `@openformulieren/sdk/utils`
+
+These will be documented with Storybook at some point.
+
+### Using the library
+
+If you decide that using the SDK itself is sufficient, then your usage comes down to:
+
+```js
+import OpenForm, {
+  ANALYTICS_PROVIDERS,
+  Formio,
+  Templates,
+  OFLibrary,
+  OpenFormsModule,
+  setCSRFToken
+} from '@open-formulieren/sdk';  // JS API
+import '@open-formulieren/sdk/styles.css';  // import the (default) stylesheet
+
+const form = new OpenForm(targetNode, options);
+form.init();
+```
+
+[backend]: https://github.com/open-formulieren/open-forms
+[docker]: https://hub.docker.com/r/openformulieren/open-forms-sdk
+[docs]: https://open-forms.readthedocs.io/en/latest/
diff --git a/README.rst b/README.rst
index 09a494370..a6c4d7d4f 100644
--- a/README.rst
+++ b/README.rst
@@ -2,8 +2,8 @@
 Open Forms SDK
 ==============
 
-:Version: 1.1.0-rc.0
-:Source: https://github.com/maykinmedia/open-forms-sdk
+:Version: 1.2.0-alpha.1
+:Source: https://github.com/open-formulieren/open-forms-sdk
 :Keywords: e-Formulieren, Common Ground, FormIO, API
 
 |docs|
diff --git a/design-tokens b/design-tokens
index e4771e59c..5761b5fc7 160000
--- a/design-tokens
+++ b/design-tokens
@@ -1 +1 @@
-Subproject commit e4771e59c44d8cd883b0edc53c75f16396cb7cb7
+Subproject commit 5761b5fc746c623494ae77999587f3771fa7c3b5
diff --git a/package.json b/package.json
index 52349430a..51067a74b 100644
--- a/package.json
+++ b/package.json
@@ -1,17 +1,40 @@
 {
-  "name": "open-forms-sdk",
-  "version": "1.1.0-rc.0",
+  "name": "@open-formulieren/sdk",
+  "version": "1.2.0-alpha.1",
   "private": true,
+  "main": "dist/open-forms-sdk.js",
+  "exports": {
+    ".": "./dist/open-forms-sdk.js",
+    "./styles.css": "./dist/open-forms-sdk.css",
+    "./components/*": "./dist/esm/components/*",
+    "./hooks/*": "./dist/esm/hooks/*",
+    "./types/*": "./dist/esm/types/*",
+    "./map/*": "./dist/esm/map/*",
+    "./errors": "./dist/esm/errors.js",
+    "./sdk": "./dist/esm/sdk.js",
+    "./utils": "./dist/esm/utils.js"
+  },
+  "files": [
+    "dist/",
+    "src/",
+    "README.rst",
+    "README.NL.rst",
+    "LICENSE.md"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/open-formulieren/open-forms-sdk.git"
+  },
+  "license": "EUPL-1.2",
   "dependencies": {
-    "@babel/core": "^7.16.0",
     "@formio/protected-eval": "^1.2.1",
     "@fortawesome/fontawesome-free": "^6.1.1",
-    "@gemeente-denhaag/button": "0.2.3-alpha.158",
-    "@gemeente-denhaag/components-css": "0.1.1-alpha.166",
-    "@gemeente-denhaag/design-tokens-components": "^0.2.3-alpha.227",
-    "@gemeente-denhaag/form-progress": "0.1.1-alpha.33",
-    "@gemeente-denhaag/icons": "0.2.3-alpha.158",
-    "@open-formulieren/design-tokens": "^0.2.0",
+    "@gemeente-denhaag/button": "0.2.3-alpha.257",
+    "@gemeente-denhaag/components-css": "0.1.1-alpha.203",
+    "@gemeente-denhaag/design-tokens-components": "0.2.3-alpha.257",
+    "@gemeente-denhaag/form-progress": "0.1.1-alpha.132",
+    "@gemeente-denhaag/icons": "0.2.3-alpha.257",
+    "@open-formulieren/design-tokens": "^0.4.0",
     "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",
     "@sentry/react": "^6.13.2",
     "@sentry/tracing": "^6.13.2",
@@ -32,70 +55,35 @@
     "camelcase": "^6.2.1",
     "case-sensitive-paths-webpack-plugin": "^2.4.0",
     "classnames": "^2.3.1",
-    "css-loader": "^6.5.1",
-    "css-minimizer-webpack-plugin": "^3.2.0",
-    "dotenv": "^10.0.0",
-    "dotenv-expand": "^5.1.0",
-    "ejs-loader": "^0.5.0",
-    "eslint": "^8.3.0",
-    "eslint-config-react-app": "^7.0.1",
-    "eslint-webpack-plugin": "^3.1.1",
-    "file-loader": "^6.2.0",
     "flatpickr": "^4.6.9",
     "formiojs": "^4.12.7",
-    "fs-extra": "^10.0.0",
-    "html-webpack-plugin": "^5.5.0",
     "ibantools": "^3.3.0",
-    "identity-obj-proxy": "^3.0.0",
     "immer": "^9.0.6",
-    "jest": "^27.4.3",
-    "jest-resolve": "^27.4.2",
-    "jest-watch-typeahead": "^1.0.0",
     "leaflet": "^1.7.1",
     "microscope-sass": "^1.0.4",
-    "mini-css-extract-plugin": "^2.4.5",
     "moment": "^2.29.1",
-    "postcss": "^8.4.4",
-    "postcss-flexbugs-fixes": "^5.0.2",
-    "postcss-loader": "^6.2.1",
-    "postcss-normalize": "^10.0.1",
-    "postcss-preset-env": "^7.0.1",
     "proj4leaflet": "^1.0.2",
-    "prompts": "^2.4.2",
-    "prop-types": "^15.7.2",
-    "react": "^17.0.2",
-    "react-app-polyfill": "^3.0.0",
-    "react-dev-utils": "^12.0.1",
-    "react-dom": "^17.0.2",
     "react-formio": "^4.3.0",
     "react-intl": "^5.20.5",
     "react-leaflet": "^3.2.2",
     "react-modal": "3.14.3",
-    "react-refresh": "^0.11.0",
     "react-router-dom": "^5.2.0",
     "react-use": "^17.2.4",
-    "resolve": "^1.20.0",
-    "resolve-url-loader": "^4.0.0",
-    "sass": "^1.32.12",
-    "sass-loader": "^12.3.0",
-    "semver": "^7.3.5",
-    "source-map-loader": "^3.0.0",
     "state-pool": "^0.6.0",
-    "style-loader": "^3.3.1",
-    "tailwindcss": "^3.0.2",
-    "terser-webpack-plugin": "^5.2.5",
-    "uri-js": "^4.4.1",
-    "use-immer": "^0.5.1",
-    "webpack": "^5.64.4",
-    "webpack-dev-server": "^4.6.0",
-    "webpack-manifest-plugin": "^4.0.2",
-    "workbox-webpack-plugin": "^6.4.1"
+    "use-immer": "^0.5.1"
+  },
+  "peerDependencies": {
+    "prop-types": "^15.7.2",
+    "react": "^16.8.0  || ^17.0.0 || ^18.0.0",
+    "react-dom": "^16.8.0  || ^17.0.0 || ^18.0.0"
   },
   "scripts": {
     "start": "yarn build:design-tokens && node scripts/start.js",
-    "build": "node scripts/build.js",
+    "build": "node scripts/build.js && yarn build:esm",
+    "build:esm": "rm -rf dist/esm && NODE_ENV=production babel --no-babelrc --config-file ./.babelrc.esm-build --ignore 'src/**/*.spec.js','src/**/fixtures/**','src/setupTests.js','src/reportWebVitals.js' src --out-dir dist/esm",
     "test": "node scripts/test.js",
     "clean": "rm -rf dist/*",
+    "prepare-package": "node scripts/prepare-package.js",
     "makemessages-en": "formatjs extract 'src/**/*.js' --format scripts/i18n-formatter.js --out-file src/i18n/messages/en.json --id-interpolation-pattern '[sha512:contenthash:base64:6]'",
     "makemessages-nl": "formatjs extract 'src/**/*.js' --format scripts/i18n-formatter.js --out-file src/i18n/messages/nl.json --id-interpolation-pattern '[sha512:contenthash:base64:6]'",
     "makemessages": "yarn run makemessages-nl && yarn run makemessages-en",
@@ -129,10 +117,72 @@
     "not op_mini all"
   ],
   "devDependencies": {
+    "@babel/cli": "^7.18.6",
+    "@babel/core": "^7.16.0",
+    "@babel/plugin-syntax-flow": "^7.18.6",
+    "@babel/plugin-transform-react-jsx": "^7.14.9",
+    "@babel/preset-react": "^7.18.6",
     "@formatjs/cli": "^4.2.33",
+    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",
+    "@svgr/webpack": "^5.5.0",
+    "@testing-library/dom": ">=7.21.4",
+    "@testing-library/jest-dom": "^5.11.4",
+    "@testing-library/react": "^11.1.0",
+    "@testing-library/user-event": "^12.1.10",
+    "babel-jest": "^27.4.2",
+    "babel-loader": "^8.2.3",
     "babel-plugin-formatjs": "^10.3.8",
+    "babel-plugin-module-resolver": "^4.1.0",
+    "babel-plugin-named-asset-import": "^0.3.8",
+    "babel-preset-react-app": "^10.0.1",
+    "bfj": "^7.0.2",
+    "browserslist": "^4.18.1",
+    "camelcase": "^6.2.1",
+    "case-sensitive-paths-webpack-plugin": "^2.4.0",
+    "css-loader": "^6.5.1",
+    "css-minimizer-webpack-plugin": "^3.2.0",
+    "dotenv": "^10.0.0",
+    "dotenv-expand": "^5.1.0",
+    "ejs-loader": "^0.5.0",
+    "eslint": "^8.3.0",
+    "eslint-config-react-app": "^7.0.1",
+    "eslint-webpack-plugin": "^3.1.1",
+    "file-loader": "^6.2.0",
+    "fs-extra": "^10.0.0",
+    "html-webpack-plugin": "^5.5.0",
+    "identity-obj-proxy": "^3.0.0",
+    "jest": "^27.4.3",
+    "jest-resolve": "^27.4.2",
+    "jest-watch-typeahead": "^1.0.0",
+    "mini-css-extract-plugin": "^2.4.5",
+    "postcss": "^8.4.4",
+    "postcss-flexbugs-fixes": "^5.0.2",
+    "postcss-loader": "^6.2.1",
+    "postcss-normalize": "^10.0.1",
+    "postcss-preset-env": "^7.0.1",
+    "prompts": "^2.4.2",
+    "prop-types": "^15.7.2",
+    "react": "^17.0.2",
+    "react-app-polyfill": "^3.0.0",
+    "react-dev-utils": "^12.0.1",
+    "react-dom": "^17.0.2",
+    "react-refresh": "^0.11.0",
     "react-test-renderer": "^17.0.2",
+    "resolve": "^1.20.0",
+    "resolve-url-loader": "^4.0.0",
+    "sass": "^1.32.12",
+    "sass-loader": "^12.3.0",
+    "semver": "^7.3.5",
+    "source-map-loader": "^3.0.0",
+    "style-loader": "^3.3.1",
+    "tailwindcss": "^3.0.2",
+    "terser-webpack-plugin": "^5.2.5",
     "typescript": "^4.4.3",
+    "uri-js": "^4.4.1",
+    "webpack": "^5.64.4",
+    "webpack-dev-server": "^4.6.0",
+    "webpack-manifest-plugin": "^4.0.2",
+    "workbox-webpack-plugin": "^6.4.1",
     "yargs": "^17.1.1"
   },
   "babel": {
diff --git a/publiccode.yaml b/publiccode.yaml
index 1a6c66f2b..e38a723da 100644
--- a/publiccode.yaml
+++ b/publiccode.yaml
@@ -7,7 +7,7 @@ publiccodeYmlVersion: '0.2'
 name: Open Forms SDK
 url: 'http://github.com/open-formulieren/open-forms-sdk.git'
 softwareType: standalone/frontend
-softwareVersion: 1.1.0-rc.0
+softwareVersion: 1.2.0-alpha.1
 releaseDate: 't.b.d.'
 logo: 'https://github.com/open-formulieren/open-forms/blob/master/docs/logo.svg'
 platforms:
diff --git a/scripts/prepare-package.js b/scripts/prepare-package.js
new file mode 100644
index 000000000..db2930e69
--- /dev/null
+++ b/scripts/prepare-package.js
@@ -0,0 +1,15 @@
+'use strict';
+
+const fs = require('fs');
+
+const paths = require('../config/paths');
+
+const packageJson = JSON.parse(fs.readFileSync(paths.appPackageJson, 'utf-8'));
+
+// make package.json modifications to be able to publish the package. Workspaces require
+// private=true, but to publish it, it must be private=false. While publishing the
+// package, we do not use workspaces.
+packageJson.private = false;
+
+const stringified = JSON.stringify(packageJson, null, 2) + "\n";
+fs.writeFileSync(paths.appPackageJson, stringified);
diff --git a/src/api.js b/src/api.js
index 3f73be118..7b5482786 100644
--- a/src/api.js
+++ b/src/api.js
@@ -3,6 +3,14 @@ import {createGlobalstate} from 'state-pool';
 import {getCSPNonce} from 'csp';
 import {getCSRFToken} from 'csrf';
 
+import {
+  APIError,
+  ValidationError,
+  NotAuthenticated,
+  PermissionDenied,
+  NotFound,
+} from './errors';
+
 const fetchDefaults = {
   credentials: 'include', // required for Firefox 60, which is used in werkplekken
 };
@@ -20,7 +28,46 @@ const updateSesionExpiry = (seconds) => {
   // TODO: we can schedule a message to be set if expiry is getting close
 };
 
-const apiCall = async (url, opts, alertOnPermissionDenied=false) => {
+const throwForStatus = async (response) => {
+  if (response.ok) return;
+
+  let responseData = null;
+  // Check if the response contains json data
+  const contentType = response.headers.get('content-type');
+  if (contentType && contentType.indexOf('application/json') !== -1) {
+    responseData = await response.json();
+  }
+
+  let ErrorClass = APIError;
+  let errorMessage = 'An API error occurred.';
+  switch (response.status) {
+    case 400: {
+      throw new ValidationError('Call did not validate on the backend', responseData || {});
+    }
+    case 401: {
+      ErrorClass = NotAuthenticated;
+      errorMessage = 'User not or no longer authenticated';
+      break;
+    }
+    case 403: {
+      ErrorClass = PermissionDenied;
+      errorMessage = 'User has insufficient permissions.';
+      break;
+    }
+    case 404: {
+      ErrorClass = NotFound;
+      errorMessage = 'Resource not found.';
+      break;
+    }
+    default: {
+      break;
+    }
+  }
+
+  throw new ErrorClass(errorMessage, response.status, responseData.detail);
+};
+
+const apiCall = async (url, opts) => {
   const options = { ...fetchDefaults, ...opts };
   if (!options.headers) options.headers = {};
 
@@ -36,17 +83,12 @@ const apiCall = async (url, opts, alertOnPermissionDenied=false) => {
   }
 
   const response = await window.fetch(url, options);
+  await throwForStatus(response);
 
   const sessionExpiry = response.headers.get(SessionExpiresInHeader);
   if (sessionExpiry) {
     updateSesionExpiry(parseInt(sessionExpiry), 10);
   }
-
-  if (response.status === 403 && alertOnPermissionDenied) {
-    const data = await response.json();
-    alert(data.detail);
-    response.json = () => Promise.resolve(data);
-  }
   return response;
 };
 
diff --git a/src/components/ErrorBoundary.js b/src/components/ErrorBoundary.js
index 50a1493a7..b0f834c5c 100644
--- a/src/components/ErrorBoundary.js
+++ b/src/components/ErrorBoundary.js
@@ -1,5 +1,11 @@
 import React from 'react';
+import PropTypes from 'prop-types';
+import {FormattedMessage} from 'react-intl';
+import { Link } from 'react-router-dom';
 
+import Anchor from 'components/Anchor';
+import Body from 'components/Body';
+import Card from 'components/Card';
 import ErrorMessage from 'components/ErrorMessage';
 
 
@@ -11,25 +17,93 @@ const logError = (error, errorInfo) => {
 class ErrorBoundary extends React.Component {
   constructor(props) {
     super(props);
-    this.state = { hasError: false };
+    this.state = { hasError: false, error: null };
   }
 
   static getDerivedStateFromError(error) {
-    return { hasError: true };
+    return {
+      hasError: true,
+      error,
+    };
   }
 
   componentDidCatch(error, errorInfo) {
+    // TODO: depending on the error type, send to sentry?
     logError(error, errorInfo);
   }
 
   render() {
-    if (!this.state.hasError) {
-      return this.props.children;
+    const { useCard, children } = this.props;
+    const { hasError, error } = this.state;
+    if (!hasError) {
+      return children;
     }
+
+    const ErrorComponent = ERROR_TYPE_MAP[error.name] || GenericError;
+    const Wrapper = useCard ? Card : React.Fragment;
     return (
-      Er ging helaas iets fout!
+      
     );
   }
 }
 
+ErrorBoundary.propTypes = {
+  useCard: PropTypes.bool,
+};
+
+
+const GenericError = ({ wrapper: Wrapper, error }) => (
+  }>
+    
+      
+    
+    {error.detail && {error.detail}}
+  
+);
+
+GenericError.propTypes = {
+  wrapper: PropTypes.elementType.isRequired,
+  error: PropTypes.object, // exception instance
+};
+
+
+const PermissionDeniedError = ({ wrapper: Wrapper, error }) => {
+  return (
+    }>
+      
+        
+      
+
+      {error.detail && {error.detail}}
+
+      
+        
+      
+
+    
+  );
+};
+
+PermissionDeniedError.propTypes = {
+  wrapper: PropTypes.elementType.isRequired,
+  error: PropTypes.object, // exception instance
+};
+
+
+// map the type of error to the component to render
+const ERROR_TYPE_MAP = {
+  'PermissionDenied': PermissionDeniedError,
+};
+
 export default ErrorBoundary;
diff --git a/src/components/Form.js b/src/components/Form.js
index 46b175f17..d372b3c66 100644
--- a/src/components/Form.js
+++ b/src/components/Form.js
@@ -141,7 +141,7 @@ const Form = ({form}) => {
     removeSubmissionId
   ] = useRecycleSubmission(form, state.submission, onSubmissionLoaded);
 
-  const [sessionExpired, resetSession] = useSessionTimeout(
+  const [sessionExpired, expiryDate, resetSession] = useSessionTimeout(
     () => {
       removeSubmissionId();
       dispatch({type: 'SESSION_EXPIRED'});
@@ -258,52 +258,58 @@ const Form = ({form}) => {
         
 
           
-            
-              
+            
+              
             
           
 
           
-            
-               dispatch({type: 'CLEAR_PROCESSING_ERROR'})}
-              />
-            
+            
+              
+                 dispatch({type: 'CLEAR_PROCESSING_ERROR'})}
+                />
+              
+            
           
 
           
-             dispatch({type: 'PROCESSING_SUCCEEDED'})}
-              component={SubmissionConfirmation}/>
+            
+               dispatch({type: 'PROCESSING_SUCCEEDED'})}
+                component={SubmissionConfirmation} />
+              
           
 
           
-            
-              
+            
+              
             
           
 
            (
-            
-               dispatch({type: 'SUBMISSION_LOADED', payload: submission})}
-                onStepSubmitted={onStepSubmitted}
-                onLogout={onLogout}
-                component={FormStep}
-              />
-            
-          )}/>
+            
+              
+                 dispatch({type: 'SUBMISSION_LOADED', payload: submission})}
+                  onStepSubmitted={onStepSubmitted}
+                  onLogout={onLogout}
+                  component={FormStep}
+                />
+              
+            
+          )} />
 
         
 
diff --git a/src/components/FormStart/index.js b/src/components/FormStart/index.js
index bbbc94f59..1d7655022 100644
--- a/src/components/FormStart/index.js
+++ b/src/components/FormStart/index.js
@@ -1,4 +1,4 @@
-import React, {useEffect, useRef} from 'react';
+import React, {useEffect, useRef, useState} from 'react';
 import PropTypes from 'prop-types';
 import {FormattedMessage, useIntl} from 'react-intl';
 
@@ -56,6 +56,7 @@ const FormStart = ({ form, onFormStart }) => {
   const doStart = useStartSubmission();
   const outagePluginId = useDetectAuthenticationOutage();
   const authErrors = useDetectAuthErrorMessages();
+  const [error, setError] = useState(null);
   const hasAuthErrors = !!outagePluginId || !!authErrors;
 
   const onFormStartCalledRef = useRef(false);
@@ -70,12 +71,25 @@ const FormStart = ({ form, onFormStart }) => {
       return;
     }
 
+    const startForm = async () => {
+      try {
+        await onFormStart();
+      } catch (e) {
+        setError(e);
+      }
+    }
+
     if (doStart && !hasAuthErrors) {
-      onFormStart();
+      startForm();
       onFormStartCalledRef.current = true;
     }
   }, [doStart, hasAuthErrors, onFormStart]);
 
+  // let errors bubble up to the error boundaries
+  if (error) {
+    throw error;
+  }
+
   // do not re-render the login options while we're redirecting
   if (doStart && !hasAuthErrors) {
     return (
diff --git a/src/components/FormStep.js b/src/components/FormStep.js
index becc7371a..996f98ab8 100644
--- a/src/components/FormStep.js
+++ b/src/components/FormStep.js
@@ -123,6 +123,7 @@ const initialState = {
   logicChecking: false,
   isFormSaveModalOpen: false,
   isNavigating: false,
+  error: null,
 };
 
 const reducer = (draft, action) => {
@@ -175,6 +176,10 @@ const reducer = (draft, action) => {
       draft.isNavigating = true;
       break;
     }
+    case 'ERROR': {
+      draft.error = action.payload;
+      break;
+    }
     default: {
       throw new Error(`Unknown action ${action.type}`);
     }
@@ -200,6 +205,7 @@ const FormStep = ({
       canSubmit, logicChecking,
       isFormSaveModalOpen,
       isNavigating,
+      error,
     },
     dispatch
   ] = useImmerReducer(reducer, initialState);
@@ -236,6 +242,11 @@ const FormStep = ({
     [submissionStep.url]
   );
 
+  // throw errors from state so the error boundaries can pick them up
+  if (error) {
+    throw error;
+  }
+
   // event loops and async programming are fun!
   // UI inputs are wonky if end-users perform input while evaluating logic checks that
   // operate on (slightly) stale form data. Evaluating the ref value once before
@@ -377,7 +388,12 @@ const FormStep = ({
 
     dispatch({type: 'NAVIGATE'});
 
-    await submitStepData(submissionStep.url, data);
+    try {
+      await submitStepData(submissionStep.url, data);
+    } catch (e) {
+      dispatch({type: 'ERROR', payload: e});
+    }
+
     // This will reload the submission
     const {submission: updatedSubmission, step} = await doLogicCheck(submissionStep.url, data);
     onLogicChecked(updatedSubmission, step); // report back to parent component
diff --git a/src/components/FormStepSummary/SummaryEditGrid.js b/src/components/FormStepSummary/SummaryEditGrid.js
new file mode 100644
index 000000000..f4cef6bb8
--- /dev/null
+++ b/src/components/FormStepSummary/SummaryEditGrid.js
@@ -0,0 +1,88 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {Utils as FormioUtils} from 'formiojs';
+import _ from 'lodash';
+
+import {getComponentLabel} from './utils';
+import {getBEMClassName} from 'utils';
+import {TableCell, TableHead, TableRow} from 'components/Table';
+import ComponentValueDisplay from './ComponentValueDisplay';
+
+const ItemInGroup = ({component, value, isLast=false}) => {
+    const label = getComponentLabel(component);
+    if (!label && !value) return null;
+
+    let componentWithValue = _.cloneDeep(component);
+    componentWithValue.value = value;
+
+    const modifiers = isLast ? [component.type, 'last'] : [component.type];
+
+    return (
+      
+        {label}
+        
+          
+        
+      
+    );
+};
+
+
+const RepeatingGroup = ({configuration, values}) => {
+  const valuesList = Object.entries(values);
+
+  return (
+    <>
+      {
+        valuesList.map(
+          ([itemKey, itemValue], index) => {
+            const childComponent = FormioUtils.getComponent(configuration, itemKey, true);
+            const isLastItemInGroup = index === valuesList.length - 1;
+            return (
+              
+            );
+          }
+        )
+      }
+    
+  );
+};
+
+
+const SummaryEditGrid = ({ component }) => {
+  const editGridlabel = getComponentLabel(component);
+  if (!editGridlabel && !component.value) return null;
+
+  // The configuration of the repeating components
+  const repeatingGroupConfig = component.components;
+  const groupsValues = component.value || [];
+
+  return (
+    <>
+      
+        {editGridlabel}
+        
+      
+      {
+        groupsValues.map((groupValues, index) => (
+          <>
+            
+              {`${component.groupLabel} ${index+1}`}
+              
+            
+            
+          
+
+        ))
+      }
+    
+  );
+};
+
+SummaryEditGrid.propTypes = {
+  component: PropTypes.object.isRequired,
+};
+
+export default SummaryEditGrid;
diff --git a/src/components/FormStepSummary/index.js b/src/components/FormStepSummary/index.js
index 629a97ea8..3ce32cf94 100644
--- a/src/components/FormStepSummary/index.js
+++ b/src/components/FormStepSummary/index.js
@@ -7,9 +7,10 @@ import Caption from 'components/Caption';
 import { Table, TableRow, TableHead, TableCell } from 'components/Table';
 import { Toolbar, ToolbarList } from 'components/Toolbar';
 import {getBEMClassName} from 'utils';
-import {getComponentLabel, iterComponentsWithData} from 'components/FormStepSummary/utils';
+import {getComponentLabel, iterComponentsWithData, isChildOfEditGrid} from 'components/FormStepSummary/utils';
 
 import ComponentValueDisplay from './ComponentValueDisplay';
+import SummaryEditGrid from './SummaryEditGrid';
 
 
 const SummaryTableRow = ({ component }) => {
@@ -64,10 +65,15 @@ const FormStepSummary = ({stepData, editStepUrl, editStepText}) => {
           * title (string), submissionStep (object), data (object), configuration (object)
           * Note that the `components` must already be flattened and non-summary display
           * components removed.
+          * Any component within an editgrid component is rendered as part of the editgrid and
+          * should not be rendered independently
           */
-          iterComponentsWithData(stepData.configuration.flattenedComponents, stepData.data).map((component) => (
-            
-          ))
+          iterComponentsWithData(stepData.configuration.flattenedComponents, stepData.data).filter(
+            (component) => !isChildOfEditGrid(component, stepData.configuration)
+          ).map((component) => {
+            let SummaryComponent = component.type === 'editgrid' ? SummaryEditGrid : SummaryTableRow;
+            return ;
+          })
         }
       
 
diff --git a/src/components/FormStepSummary/utils.js b/src/components/FormStepSummary/utils.js
index edcfc11b8..5777260b0 100644
--- a/src/components/FormStepSummary/utils.js
+++ b/src/components/FormStepSummary/utils.js
@@ -1,4 +1,5 @@
 import React from 'react';
+import {Utils as FormioUtils} from 'formiojs';
 
 const iterComponentsWithData = (components, data) => {
   // Iterate over (pre-flattened) components and return key/values
@@ -19,7 +20,8 @@ const getComponentLabel = (component) => {
   const {label, type} = component;
 
   switch (type) {
-    case 'fieldset' : {
+    case 'fieldset' :
+    case 'editgrid' : {
       if (component.hideHeader) return '';
       return ({label});
     }
@@ -49,4 +51,21 @@ const humanFileSize = (size) => {
   return {size: newSize, unit};
 };
 
-export {iterComponentsWithData, getComponentLabel, humanFileSize};
+// Duplicate code from Open Forms Admin
+const isChildOfEditGrid = (component, configuration) => {
+  // Get all edit grids in the configuration
+  let editGrids = [];
+  FormioUtils.eachComponent(configuration.components, configComponent => {
+    if (configComponent.type === 'editgrid') editGrids.push(configComponent);
+  });
+
+  // Check if our component is in the editgrid
+  for (const editGrid of editGrids) {
+    const foundComponent = FormioUtils.getComponent(editGrid.components, component.key, true);
+    if (foundComponent) return true;
+  }
+
+  return false;
+};
+
+export {iterComponentsWithData, getComponentLabel, humanFileSize, isChildOfEditGrid};
diff --git a/src/components/Sessions/RequireSession.js b/src/components/Sessions/RequireSession.js
index 6c5bbe1ce..a85387d73 100644
--- a/src/components/Sessions/RequireSession.js
+++ b/src/components/Sessions/RequireSession.js
@@ -1,42 +1,142 @@
-import React from 'react';
+import React, {useContext, useEffect, useState} from 'react';
 import PropTypes from 'prop-types';
 import {Link} from 'react-router-dom';
-import {FormattedMessage} from 'react-intl';
+import {FormattedMessage, FormattedRelativeTime} from 'react-intl';
+import useTimeout from 'react-use/esm/useTimeout';
+import useTimeoutFn from 'react-use/esm/useTimeoutFn';
 
+import {apiCall} from 'api';
 import Anchor from 'components/Anchor';
 import Card from 'components/Card';
 import ErrorMessage from 'components/ErrorMessage';
+import Modal from 'components/modals/Modal';
+import {Toolbar, ToolbarList} from 'components/Toolbar';
+import Button from 'components/Button';
+import {ConfigContext} from 'Context';
 
-// import SessionExpiry from './SessionExpiry';
+const WARN_SESSION_TIMEOUT_FACTOR = 0.9; // once 90% of the session expiry time has passed, show a warning
 
+const RelativeTimeToExpiry = ({numSeconds}) => {
+  // more than 24 hours -> don't bother
+  if (numSeconds >= 3600 * 24) return null;
+  return (
+    
+  );
+};
+
+RelativeTimeToExpiry.propTypes = {
+  numSeconds: PropTypes.number.isRequired,
+};
+
+const useTriggerWarning = (numSeconds) => {
+  let timeout;
+
+  const [showWarning, setShowWarning] = useState(false);
 
-const RequireSession = ({ expired=false, children }) => {
-  if (!expired) {
+  // no time available
+  if (numSeconds == null) {
+    timeout = 10 * 3600 * 1000; // 10 hours as a fallback
+  } else {
+    // re-render WARN_SESSION_TIMEOUT_FACTOR before the session expires to show a warning
+    timeout = WARN_SESSION_TIMEOUT_FACTOR * numSeconds * 1000;
+  }
+  const reset = useTimeoutFn(() => setShowWarning(true), timeout)[2];
+  return [
+    showWarning,
+    () => {
+      setShowWarning(false);
+      reset();
+    },
+  ];
+};
+
+const RequireSession = ({expired = false, expiryDate = null, children}) => {
+  const {baseUrl} = useContext(ConfigContext);
+  const [warningDismissed, setWarningDismissed] = useState(false);
+
+  // re-render when the session is expired to show the error message
+  const now = new Date();
+  const timeToExpiryInMS = expiryDate ? Math.max(expiryDate - now, 0) : 1000 * 3600 * 10; // 10 hour fallback in case there's no date
+  const [, cancelExpiryTimeout, resetExpiryTimeout] = useTimeout(timeToExpiryInMS);
+  const [warningTriggered, resetWarningTriggered] = useTriggerWarning(timeToExpiryInMS / 1000);
+
+  // reset if a new timeout date is received
+  useEffect(() => {
+    if (!expiryDate) {
+      cancelExpiryTimeout();
+      return;
+    }
+    resetExpiryTimeout();
+    resetWarningTriggered();
+    setWarningDismissed(false);
+    return () => {
+      cancelExpiryTimeout();
+    };
+  // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [expiryDate]);
+
+  if (expired) {
     return (
-      <>
-        {children}
-        {/* disabled for now until we figure out how to style this */}
-      
+      }>
+        
+           {chunks},
+            }}
+          />
+        
+      
     );
-  };
+  }
+
+  // if we don't have any expiry date information, we can't show anything -> abort early.
+  if (expiryDate == null) return children;
 
+  const showWarning = !warningDismissed && warningTriggered;
+  const secondsToExpiry = parseInt((expiryDate - now) / 1000);
   return (
-    }>
-      
-         {chunks},
-          }}
-        />
-      
-    
-  );
+    <>
+      }
+        isOpen={showWarning}
+        closeModal={() => {
+          setWarningDismissed(true);
+        }}
+      >
+        
+          ,
+            }}/>
+        
+        
+          
+            
+          
+        
+      
+      {children}
+    
+  )
 };
 
 RequireSession.propTypes = {
   expired: PropTypes.bool,
+  expiryDate: PropTypes.instanceOf(Date),
   children: PropTypes.node,
 };
 
diff --git a/src/components/Sessions/SessionExpiry.js b/src/components/Sessions/SessionExpiry.js
deleted file mode 100644
index 9e1c80818..000000000
--- a/src/components/Sessions/SessionExpiry.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react';
-import {useGlobalState} from 'state-pool';
-import {FormattedRelativeTime, FormattedMessage} from 'react-intl';
-
-import {sessionExpiresAt} from 'api';
-
-
-const SessionExpiry = () => {
-  const [expiryDate] = useGlobalState(sessionExpiresAt);
-  if (!expiryDate) return null;
-
-  const seconds = (expiryDate - new Date()) / 1000;
-  const delta = (
-    
-  );
-  return (
-    
-  );
-};
-
-SessionExpiry.propTypes = {
-};
-
-
-export default SessionExpiry;
diff --git a/src/components/modals/Modal.js b/src/components/modals/Modal.js
index fabcac3c8..5176173b4 100644
--- a/src/components/modals/Modal.js
+++ b/src/components/modals/Modal.js
@@ -1,7 +1,7 @@
 import React, {useEffect} from 'react';
 import PropTypes from 'prop-types';
 import ReactModal from 'react-modal';
-import {FormattedMessage} from 'react-intl';
+import {useIntl} from 'react-intl';
 
 import FAIcon from 'components/FAIcon';
 import {getBEMClassName} from 'utils';
@@ -28,6 +28,7 @@ const Modal = ({
   children,
 }) => {
     usePreventScroll(isOpen);
+    const intl = useIntl();
     return (
         }
+                title={intl.formatMessage({
+                  description: 'Modal close icon title',
+                  defaultMessage: 'Close',
+                })}
                 onClick={closeModal}
               />
             
diff --git a/src/errors.js b/src/errors.js
index 28eab722a..ebedf9efc 100644
--- a/src/errors.js
+++ b/src/errors.js
@@ -34,3 +34,15 @@ export class ValidationError extends ExtendableError {
   }
 
 };
+
+export class APIError extends ExtendableError {
+  constructor(message, statusCode, detail) {
+    super(message);
+    this.statusCode = statusCode;
+    this.detail = detail;
+  }
+}
+
+export class NotAuthenticated extends APIError {}
+export class PermissionDenied extends APIError {}
+export class NotFound extends APIError {}
diff --git a/src/formio/components/EditGrid.js b/src/formio/components/EditGrid.js
new file mode 100644
index 000000000..aa060f263
--- /dev/null
+++ b/src/formio/components/EditGrid.js
@@ -0,0 +1,132 @@
+import { Formio } from 'react-formio';
+
+import { applyPrefix } from '../utils';
+
+const FormioEditGrid = Formio.Components.components.editgrid;
+
+const EditRowState = {
+  New: 'new',
+  Editing: 'editing',
+  Saved: 'saved',
+  Viewing: 'viewing',
+  Removed: 'removed',
+  Draft: 'draft',
+};
+
+
+class EditGrid extends FormioEditGrid {
+
+  get inputInfo() {
+    const info = super.inputInfo;
+    // change the default CSS classes
+    info.attr.class = applyPrefix('editgrid');
+    return info;
+  }
+
+  renderRow(row, rowIndex) {
+    // Taken from Formio: https://github.com/formio/formio.js/blob/4.13.x/src/components/editgrid/EditGrid.js#L430
+    // Overwritten so that we can use a custom template for each collapsed row
+    const dataValue = this.dataValue;
+    if (this.isOpen(row)) {
+      return this.renderComponents(row.components);
+    }
+    else {
+      const flattenedComponents = this.flattenComponents(rowIndex);
+
+      return this.renderTemplate(
+        'editgridrow',
+        {
+          row: dataValue[rowIndex] || {},
+          data: this.data,
+          rowIndex,
+          components: this.component.components,
+          flattenedComponents,
+          displayValue: (component) => this.displayComponentValue(component),
+          isVisibleInRow: (component) => this.isComponentVisibleInRow(component, flattenedComponents),
+          getView: (component, data) => {
+            const instance = flattenedComponents[component.key];
+            const view = instance ? instance.getView(data || instance.dataValue) : '';
+
+            // If there is an html tag in view, don't allow it to be injected in template
+            const htmlTagRegExp = new RegExp('<(.*?)>');
+            return typeof view === 'string' && view.length && !instance.component?.template && htmlTagRegExp.test(view)
+            ? ``
+            : view;
+          },
+          state: this.editRows[rowIndex].state,
+          t: this.t.bind(this)
+        },
+      );
+    }
+  }
+
+  // shouldValidateDraft and shouldValidateRow are copied from Formio, because when calling validateRow, 'this' seems
+  // to only have methods defined on Component and not EditGrid
+  shouldValidateDraft(editRow) {
+    // Draft rows should be validated only when there was an attempt to submit a form
+    return (editRow.state === EditRowState.Draft &&
+      !this.pristine &&
+      !this.root?.pristine &&
+      !this.hasOpenRows()) ||
+      this.root?.submitted;
+  }
+
+  // shouldValidateDraft and shouldValidateRow are copied from Formio, because when calling validateRow, 'this' seems
+  // to only have methods defined on Component and not EditGrid
+  shouldValidateRow(editRow, dirty) {
+    return this.shouldValidateDraft(editRow) ||
+      editRow.state === EditRowState.Editing ||
+      dirty;
+  }
+
+  // Overwritten from Formio, because we have the case where some components can have Async validation
+  // In this case we need to wait for the promise to be resolved before returning
+  validateRow(editRow, dirty) {
+    let valid = true;
+    const errorsSnapshot = [...this.errors];
+
+    if (this.shouldValidateRow(editRow, dirty)) {
+      editRow.components.forEach(comp => {
+        if (!this.component.rowDrafts) {
+          comp.setPristine(!dirty);
+        }
+
+        const silentCheck = this.component.rowDrafts && !this.shouldValidateDraft(editRow);
+
+        if (comp.component.validate.plugins && comp.component.validate.plugins.length) {
+          valid &= comp.checkAsyncValidity(null, dirty, editRow.data, silentCheck);
+        } else {
+          valid &= comp.checkValidity(null, dirty, editRow.data, silentCheck);
+        }
+      });
+    }
+
+    if (this.component.validate && this.component.validate.row) {
+      valid = this.evaluate(this.component.validate.row, {
+        valid,
+        row: editRow.data
+      }, 'valid', true);
+      if (valid.toString() !== 'true') {
+        editRow.error = valid;
+        valid = false;
+      }
+      else {
+        editRow.error = null;
+      }
+      if (valid === null) {
+        valid = `Invalid row validation for ${this.key}`;
+      }
+    }
+
+    editRow.errors = !valid ? this.errors.filter((err) => !errorsSnapshot.includes(err)) : null;
+
+    if (!this.component.rowDrafts || this.root?.submitted) {
+      this.showRowErrorAlerts(editRow, !!valid);
+    }
+
+    return !!valid;
+  }
+}
+
+
+export default EditGrid;
diff --git a/src/formio/components/Number.js b/src/formio/components/Number.js
index 56eafbac5..58051dade 100644
--- a/src/formio/components/Number.js
+++ b/src/formio/components/Number.js
@@ -20,7 +20,11 @@ class Number extends Formio.Components.components.number {
   }
 
   checkComponentValidity(data, dirty, row, options = {}){
-    return super.checkComponentValidity(data, dirty, row, {...options, async: true});
+    let updatedOptions = {...options};
+    if (this.component.validate.plugins && this.component.validate.plugins.length) {
+      updatedOptions.async = true;
+    }
+    return super.checkComponentValidity(data, dirty, row, updatedOptions);
   }
 }
 
diff --git a/src/formio/components/PhoneNumberField.js b/src/formio/components/PhoneNumberField.js
index 422b69b16..8c9fe1066 100644
--- a/src/formio/components/PhoneNumberField.js
+++ b/src/formio/components/PhoneNumberField.js
@@ -41,7 +41,11 @@ class PhoneNumberField extends PhoneNumber {
     }
 
   checkComponentValidity(data, dirty, row, options = {}){
-    return super.checkComponentValidity(data, dirty, row, {...options, async: true});
+    let updatedOptions = {...options};
+    if (this.component.validate.plugins && this.component.validate.plugins.length) {
+      updatedOptions.async = true;
+    }
+    return super.checkComponentValidity(data, dirty, row, updatedOptions);
   }
 }
 
diff --git a/src/formio/components/TextField.js b/src/formio/components/TextField.js
index 1ca1c6d74..91bd0a033 100644
--- a/src/formio/components/TextField.js
+++ b/src/formio/components/TextField.js
@@ -26,7 +26,11 @@ class TextField extends Formio.Components.components.textfield {
   }
 
   checkComponentValidity(data, dirty, row, options = {}){
-    return super.checkComponentValidity(data, dirty, row, {...options, async: true});
+    let updatedOptions = {...options};
+    if (this.component.validate.plugins && this.component.validate.plugins.length) {
+      updatedOptions.async = true;
+    }
+    return super.checkComponentValidity(data, dirty, row, updatedOptions);
   }
 
   setLocationData(postcode, house_number, key) {
@@ -46,14 +50,18 @@ class TextField extends Formio.Components.components.textfield {
     const isValidPostcode = POSTCODE_REGEX.test(data[this.component.derivePostcode]);
 
     if (isValidHouseNumber && isValidPostcode) {
-      if (this.component.deriveStreetName) {
+      // Fill data if it is not set yet or if the field is readonly (i.e. Formio's disabled).
+      // Unrelated to the HTML 'disabled' attribute.
+      const mayAutofillValue = !this.getValue() || this.component.disabled;
+
+      if (this.component.deriveStreetName && mayAutofillValue) {
         this.setLocationData(
           data[this.component.derivePostcode],
           data[this.component.deriveHouseNumber],
           'streetName'
         );
       }
-      if (this.component.deriveCity) {
+      if (this.component.deriveCity && mayAutofillValue) {
         this.setLocationData(
           data[this.component.derivePostcode],
           data[this.component.deriveHouseNumber],
diff --git a/src/formio/module.js b/src/formio/module.js
index 564ddc61d..cb884b2d9 100644
--- a/src/formio/module.js
+++ b/src/formio/module.js
@@ -20,6 +20,7 @@ import Map from './components/Map';
 import PasswordField from './components/Password';
 import LicensePlateField from './components/LicensePlateField';
 import CoSign from './components/CoSign';
+import EditGrid from './components/EditGrid';
 
 const FormIOModule = {
   components: {
@@ -45,6 +46,7 @@ const FormIOModule = {
     password: PasswordField,
     licenseplate: LicensePlateField,
     coSign: CoSign,
+    editgrid: EditGrid,
   },
 };
 
diff --git a/src/formio/templates/editGrid.ejs b/src/formio/templates/editGrid.ejs
new file mode 100644
index 000000000..60807f94c
--- /dev/null
+++ b/src/formio/templates/editGrid.ejs
@@ -0,0 +1,43 @@
+
+
{{ ctx.component.label }}
+ +
+
    + {% ctx.rows.forEach(function(row, rowIndex) { %} +
    +
  • + {{row}} + + {% if (ctx.openRows[rowIndex] && !ctx.readOnly) { %} +
    +
      +
    • + +
    • + {% if (ctx.component.removeRow) { %} +
    • + +
    • + {% } %} +
    +
    + {% } %} + + +
    +
    {{ctx.errors[rowIndex]}}
    +
    +
  • +
    + {% }) %} +
+
+ + {% if (!ctx.readOnly && ctx.hasAddButton) { %} +
+ +
+ {% } %} +
diff --git a/src/formio/templates/editGridRow.ejs b/src/formio/templates/editGridRow.ejs new file mode 100644 index 000000000..42118220c --- /dev/null +++ b/src/formio/templates/editGridRow.ejs @@ -0,0 +1,18 @@ +
+ {% for (const key in ctx.flattenedComponents) { %} +
{{ctx.flattenedComponents[key].label}}: {{ ctx.getView(ctx.flattenedComponents[key], ctx.row[key]) }}
+ {% } %} + + {% if (!ctx.self.options.readOnly) { %} +
+
    +
  • + +
  • +
  • + +
  • +
+
+ {% } %} +
diff --git a/src/formio/templates/library.js b/src/formio/templates/library.js index 96594f5be..9327db495 100644 --- a/src/formio/templates/library.js +++ b/src/formio/templates/library.js @@ -15,6 +15,8 @@ import { default as FileTemplate } from './file.ejs'; import { default as MapTemplate } from './map.ejs'; import { default as MultiValueRowTemplate } from './multiValueRow.ejs'; import { default as MultiValueTableTemplate } from './multiValueTable.ejs'; +import { default as EditGridTemplate } from './editGrid.ejs'; +import { default as EditGridRowTemplate } from './editGridRow.ejs'; const OFLibrary = { component: {form: ComponentTemplate}, @@ -34,6 +36,8 @@ const OFLibrary = { map: {form: MapTemplate}, multiValueRow: {form: MultiValueRowTemplate}, multiValueTable: {form: MultiValueTableTemplate}, + editgrid: {form: EditGridTemplate}, + editgridrow: {form: EditGridRowTemplate}, }; diff --git a/src/formio/validators/plugins.js b/src/formio/validators/plugins.js index 8ccbc8b25..f42b86fce 100644 --- a/src/formio/validators/plugins.js +++ b/src/formio/validators/plugins.js @@ -27,7 +27,7 @@ const pluginAPIValidator = (plugin) => { check(component, setting, value) { if (!value) return true; - const {baseUrl} = component.currentForm.options; + const {baseUrl} = component.currentForm?.options || component.options; const url = `${baseUrl}validation/plugins/${plugin}`; return ( post(url, {value}) diff --git a/src/hooks/useSessionTimeout.js b/src/hooks/useSessionTimeout.js index 09525a4ef..946f962cd 100644 --- a/src/hooks/useSessionTimeout.js +++ b/src/hooks/useSessionTimeout.js @@ -54,7 +54,7 @@ const useSessionTimeout = (onTimeout) => { setExpiryDate(null); } - return [expired, reset]; + return [expired, expiryDate, reset]; }; export default useSessionTimeout; diff --git a/src/i18n/compiled/en.json b/src/i18n/compiled/en.json index 709059b01..7aa76f1bd 100644 --- a/src/i18n/compiled/en.json +++ b/src/i18n/compiled/en.json @@ -23,6 +23,12 @@ "value": "Appointment cancellation failed" } ], + "6GNK65": [ + { + "type": 0, + "value": "There was an authentication and/or permission problem." + } + ], "6sTcEM": [ { "type": 0, @@ -75,6 +81,12 @@ "value": "This form is currently undergoing maintenance and can not be accessed at the moment." } ], + "DK2ewv": [ + { + "type": 0, + "value": "Authentication problem" + } + ], "DvlDIB": [ { "type": 0, @@ -137,6 +149,18 @@ "value": "Your email address" } ], + "KMs17v": [ + { + "type": 0, + "value": "Unfortunately something went wrong!" + } + ], + "KvSkZT": [ + { + "type": 0, + "value": "Oops!" + } + ], "LEeGSv": [ { "options": { @@ -177,6 +201,12 @@ "value": "Payment is required for this product" } ], + "Qu8SM1": [ + { + "type": 0, + "value": "Extend" + } + ], "R5NUuU": [ { "type": 0, @@ -205,16 +235,6 @@ "value": "no" } ], - "STW5dH": [ - { - "type": 0, - "value": "Your session will expire " - }, - { - "type": 1, - "value": "delta" - } - ], "UWylpR": [ { "type": 0, @@ -321,6 +341,12 @@ "value": "Your payment is received and processed." } ], + "dFTtOX": [ + { + "type": 0, + "value": "Back to form start" + } + ], "eO6Ysb": [ { "type": 0, @@ -363,6 +389,26 @@ "value": "Your appointment has been cancelled" } ], + "mMYAWl": [ + { + "type": 0, + "value": "Your session is about to expire " + }, + { + "type": 1, + "value": "delta" + }, + { + "type": 0, + "value": ". Extend your session if you wish to continue." + } + ], + "nwQjsz": [ + { + "type": 0, + "value": "Your session will expire soon." + } + ], "oMvPQU": [ { "type": 0, diff --git a/src/i18n/compiled/nl.json b/src/i18n/compiled/nl.json index b645b5f15..308da7d91 100644 --- a/src/i18n/compiled/nl.json +++ b/src/i18n/compiled/nl.json @@ -23,6 +23,12 @@ "value": "Afspraak annuleren mislukt" } ], + "6GNK65": [ + { + "type": 0, + "value": "U moet ingelogd zijn voor deze actie." + } + ], "6sTcEM": [ { "type": 0, @@ -75,6 +81,12 @@ "value": "Dit formulier is momenteel in onderhoud en daardoor tijdelijk niet beschikbaar." } ], + "DK2ewv": [ + { + "type": 0, + "value": "Inlogprobleem" + } + ], "DvlDIB": [ { "type": 0, @@ -137,6 +149,18 @@ "value": "Uw e-mailadres" } ], + "KMs17v": [ + { + "type": 0, + "value": "Er ging helaas iets fout!" + } + ], + "KvSkZT": [ + { + "type": 0, + "value": "Oeps!" + } + ], "LEeGSv": [ { "options": { @@ -205,16 +229,6 @@ "value": "nee" } ], - "STW5dH": [ - { - "type": 0, - "value": "Uw sessie vervalt " - }, - { - "type": 1, - "value": "delta" - } - ], "UWylpR": [ { "type": 0, @@ -321,6 +335,12 @@ "value": "Uw betaling is ontvangen en verwerkt." } ], + "dFTtOX": [ + { + "type": 0, + "value": "Terug naar begin" + } + ], "eO6Ysb": [ { "type": 0, diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index fd85ec646..b07793d73 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -19,6 +19,11 @@ "description": "Appointment cancellation error message", "originalDefault": "Appointment cancellation failed" }, + "6GNK65": { + "defaultMessage": "There was an authentication and/or permission problem.", + "description": "Authentication error message", + "originalDefault": "There was an authentication and/or permission problem." + }, "6sTcEM": { "defaultMessage": "Pay now", "description": "Start payment button", @@ -59,6 +64,11 @@ "description": "Maintenance mode message", "originalDefault": "This form is currently undergoing maintenance and can not be accessed at the moment." }, + "DK2ewv": { + "defaultMessage": "Authentication problem", + "description": "'Permission denied' error title", + "originalDefault": "Authentication problem" + }, "DvlDIB": { "defaultMessage": "Are you sure that you want to logout?", "description": "log out confirmation prompt", @@ -104,6 +114,16 @@ "description": "Form save modal email field label", "originalDefault": "Your email address" }, + "KMs17v": { + "defaultMessage": "Unfortunately something went wrong!", + "description": "Generic error message", + "originalDefault": "Unfortunately something went wrong!" + }, + "KvSkZT": { + "defaultMessage": "Oops!", + "description": "Error boundary title", + "originalDefault": "Oops!" + }, "LEeGSv": { "defaultMessage": "{isApplicable, select, false {{label} (n/a)} other {{label}} }", "description": "Step label in progress indicator", @@ -119,6 +139,11 @@ "description": "Payment required info text", "originalDefault": "Payment is required for this product" }, + "Qu8SM1": { + "defaultMessage": "Extend", + "description": "Extend session button (in modal)", + "originalDefault": "Extend" + }, "R5NUuU": { "defaultMessage": "Confirm", "description": "Form save modal submit button", @@ -139,11 +164,6 @@ "description": "'False' display", "originalDefault": "no" }, - "STW5dH": { - "defaultMessage": "Your session will expire {delta}", - "description": "Session expiry timer", - "originalDefault": "Your session will expire {delta}" - }, "UWylpR": { "defaultMessage": "This form is temporarily unavailable because of an outage with the {label} authentication service. Please try again later.", "description": "Authentication outage message", @@ -204,6 +224,11 @@ "description": "payment registered status", "originalDefault": "Your payment is received and processed." }, + "dFTtOX": { + "defaultMessage": "Back to form start", + "description": "return to form start link after 403", + "originalDefault": "Back to form start" + }, "eO6Ysb": { "defaultMessage": "Your payment is currently processing.", "description": "payment processing status", @@ -239,6 +264,16 @@ "description": "Appointment cancellated body", "originalDefault": "Your appointment has been cancelled" }, + "mMYAWl": { + "defaultMessage": "Your session is about to expire {delta}. Extend your session if you wish to continue.", + "description": "Session expiry warning message (in modal)", + "originalDefault": "Your session is about to expire {delta}. Extend your session if you wish to continue." + }, + "nwQjsz": { + "defaultMessage": "Your session will expire soon.", + "description": "Session expiry warning title (in modal)", + "originalDefault": "Your session will expire soon." + }, "oMvPQU": { "defaultMessage": "Your session has expired", "description": "Session expired card title", diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index fe9381a78..cbaf81820 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -19,6 +19,11 @@ "description": "Appointment cancellation error message", "originalDefault": "Appointment cancellation failed" }, + "6GNK65": { + "defaultMessage": "U moet ingelogd zijn voor deze actie.", + "description": "Authentication error message", + "originalDefault": "There was an authentication and/or permission problem." + }, "6sTcEM": { "defaultMessage": "Nu betalen", "description": "Start payment button", @@ -59,6 +64,11 @@ "description": "Maintenance mode message", "originalDefault": "This form is currently undergoing maintenance and can not be accessed at the moment." }, + "DK2ewv": { + "defaultMessage": "Inlogprobleem", + "description": "'Permission denied' error title", + "originalDefault": "Authentication problem" + }, "DvlDIB": { "defaultMessage": "Weet u zeker dat u wilt uitloggen?", "description": "log out confirmation prompt", @@ -104,6 +114,16 @@ "description": "Form save modal email field label", "originalDefault": "Your email address" }, + "KMs17v": { + "defaultMessage": "Er ging helaas iets fout!", + "description": "Generic error message", + "originalDefault": "Unfortunately something went wrong!" + }, + "KvSkZT": { + "defaultMessage": "Oeps!", + "description": "Error boundary title", + "originalDefault": "Oops!" + }, "LEeGSv": { "defaultMessage": "{isApplicable, select, false {{label} (n.v.t.)} other {{label}} }", "description": "Step label in progress indicator", @@ -139,11 +159,6 @@ "description": "'False' display", "originalDefault": "no" }, - "STW5dH": { - "defaultMessage": "Uw sessie vervalt {delta}", - "description": "Session expiry timer", - "originalDefault": "Your session will expire {delta}" - }, "UWylpR": { "defaultMessage": "Dit formulier is tijdelijk niet beschikbaar wegens een storing bij de {label} authenticatieservice. Gelieve later opnieuw te proberen.", "description": "Authentication outage message", @@ -204,6 +219,11 @@ "description": "payment registered status", "originalDefault": "Your payment is received and processed." }, + "dFTtOX": { + "defaultMessage": "Terug naar begin", + "description": "return to form start link after 403", + "originalDefault": "Back to form start" + }, "eO6Ysb": { "defaultMessage": "Uw betaling wordt momenteel verwerkt.", "description": "payment processing status", diff --git a/src/jstests/formio/components/textfield.spec.js b/src/jstests/formio/components/textfield.spec.js index f80307c36..1636c5f63 100644 --- a/src/jstests/formio/components/textfield.spec.js +++ b/src/jstests/formio/components/textfield.spec.js @@ -32,8 +32,10 @@ describe('TextField Component', () => { }).catch(done); }); - test('TextField with address prefill refreshes city on invalid data', (done) => { + test('TextField (readonly) with address prefill refreshes city on invalid data', (done) => { let formJSON = _.cloneDeep(addressPrefillForm); + formJSON.components[2].disabled = true; + formJSON.components[3].disabled = true; apiModule.get.mockResolvedValue({}); const element = document.createElement('div'); @@ -53,6 +55,27 @@ describe('TextField Component', () => { }).catch(done); }); + test('TextField (editable) with address prefill does not modify city if already filled', (done) => { + let formJSON = _.cloneDeep(addressPrefillForm); + apiModule.get.mockResolvedValue({}); + + const element = document.createElement('div'); + + Formio.createForm(element, formJSON).then(form => { + form.setPristine(false); + const componentCity = form.getComponent('city'); + componentCity.setValue('Amsterdam'); + + componentCity.handleSettingLocationData({postcode: '0000AA', houseNumber: '0'}); + + setTimeout(() => { + expect(componentCity.getValue()).toEqual('Amsterdam'); + done(); + }, 300); + + }).catch(done); + }); + test('Address prefill street', (done) => { let formJSON = _.cloneDeep(addressPrefillForm); apiModule.get.mockResolvedValue({city: 'Amsterdam', streetName: 'Beautiful Street'}); @@ -72,8 +95,10 @@ describe('TextField Component', () => { }).catch(done); }); - test('TextField with address prefill refreshes street on invalid data', (done) => { + test('TextField (readonly) with address prefill refreshes street on invalid data', (done) => { let formJSON = _.cloneDeep(addressPrefillForm); + formJSON.components[2].disabled = true; + formJSON.components[3].disabled = true; apiModule.get.mockResolvedValue({}); const element = document.createElement('div'); @@ -92,4 +117,26 @@ describe('TextField Component', () => { }).catch(done); }); + + test('TextField (editable) with address prefill (invalid data) does not modify street if already filled', (done) => { + let formJSON = _.cloneDeep(addressPrefillForm); + apiModule.get.mockResolvedValue({}); + + const element = document.createElement('div'); + + Formio.createForm(element, formJSON).then(form => { + form.setPristine(false); + const componentStreet = form.getComponent('streetName'); + componentStreet.setValue('Beautiful Street'); + + componentStreet.handleSettingLocationData({postcode: '0000AA', houseNumber: '0'}); + + setTimeout(() => { + expect(componentStreet.getValue()).toEqual('Beautiful Street'); + done(); + }, 300); + + }).catch(done); + }); + }); diff --git a/src/scss/components/_alert.scss b/src/scss/components/_alert.scss index 4d66c970b..0272b1c4b 100644 --- a/src/scss/components/_alert.scss +++ b/src/scss/components/_alert.scss @@ -8,15 +8,22 @@ justify-content: flex-start; @include modifier('info') { - // @include color-info('background-color'); - background-color: #d9ebf7; + background-color: var(--of-alert-info-bg); } @include modifier('error') { - background-color: #f8d7da; // from formio.form.css .formio-error + background-color: var(--of-alert-error-bg); .fa-icon { - @include color-danger; + color: var(--of-color-danger); + } + } + + @include modifier('warning') { + background-color: var(--of-alert-warning-bg); + + .fa-icon { + color: var(--of-color-warning); } } diff --git a/src/scss/components/_editgrid.scss b/src/scss/components/_editgrid.scss new file mode 100644 index 000000000..9fd0f9984 --- /dev/null +++ b/src/scss/components/_editgrid.scss @@ -0,0 +1,42 @@ +@import '~microscope-sass/lib/color'; +@import '~microscope-sass/lib/typography'; + +@import '../mixins/prefix'; + + +.#{prefix(editgrid)} { + @include body; + + ul { + padding: 0; + } + + &__label { + @include h3; + } + + &__group { + padding: $grid-margin-4; + border-style: solid; + border-width: thin; + border-color: $color-border; + + &:not(:first-child) { + border-top-style: none; + } + } + + &__list { + list-style: none; + } + + &__group-actions { + padding-top: $grid-margin-2; + } + + &__add-button { + display: flex; + width: 100%; + padding: $grid-margin-2 0; + } +} diff --git a/src/scss/components/_summary-row.scss b/src/scss/components/_summary-row.scss index f57b3e621..efba52645 100644 --- a/src/scss/components/_summary-row.scss +++ b/src/scss/components/_summary-row.scss @@ -7,7 +7,7 @@ $block: '.#{prefix('summary-row')}'; #{$block} { - @include modifier('fieldset') { + &--fieldset, &--editgrid { &:not(:first-child) { td, th { @@ -21,4 +21,10 @@ $block: '.#{prefix('summary-row')}'; } } } + + @include modifier('last') { + td, th { + @include margin(true, $properties: padding-bottom); + } + } } diff --git a/src/sdk.js b/src/sdk.js index 26662b167..34f80abb9 100644 --- a/src/sdk.js +++ b/src/sdk.js @@ -101,6 +101,7 @@ class OpenForm { this.formObject = formObject; // render the wrapping React component + // TODO: make this work with React 18 which has a different react-dom API ReactDOM.render( diff --git a/src/styles.scss b/src/styles.scss index fb6e8d931..6139fb6dc 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -56,3 +56,4 @@ @import "./scss/components/co-sign"; @import "./scss/components/leaflet-map"; @import "./scss/components/signature"; +@import "./scss/components/editgrid"; diff --git a/yarn.lock b/yarn.lock index 82e161a0c..01e4af9ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,6 +19,22 @@ jsonpointer "^5.0.0" leven "^3.1.0" +"@babel/cli@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.18.6.tgz#b1228eb9196b34d608155a47508011d9e47ab1f2" + integrity sha512-jXNHoYCbxZ8rKy+2lyy0VjcaGxS4NPbN0qc95DjIiGZQL/mTNx3o2/yI0TG+X0VrrTuwmO7zH52T9NcNdbF9Uw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.8" + commander "^4.0.1" + convert-source-map "^1.1.0" + fs-readdir-recursive "^1.1.0" + glob "^7.0.0" + make-dir "^2.1.0" + slash "^2.0.0" + optionalDependencies: + "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3" + chokidar "^3.4.0" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.8.3": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" @@ -135,6 +151,13 @@ dependencies: "@babel/types" "^7.16.7" +"@babel/helper-annotate-as-pure@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" + integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== + dependencies: + "@babel/types" "^7.18.6" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz#38d138561ea207f0f69eb1626a418e4f7e6a580b" @@ -283,6 +306,13 @@ dependencies: "@babel/types" "^7.15.4" +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + "@babel/helper-module-transforms@^7.15.4": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.15.4.tgz#962cc629a7f7f9a082dd62d0307fa75fe8788d7c" @@ -340,6 +370,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== +"@babel/helper-plugin-utils@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.6.tgz#9448974dd4fb1d80fefe72e8a0af37809cd30d6d" + integrity sha512-gvZnm1YAAxh13eJdkb9EWHBnF3eAub3XTLCZEehHT2kWxiKVRL64+ae5Y6Ivne0mVHmMYKT+xWgZO+gQhuLUBg== + "@babel/helper-remap-async-to-generator@^7.16.8": version "7.16.8" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz#29ffaade68a367e2ed09c90901986918d25e57e3" @@ -420,6 +455,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== +"@babel/helper-validator-identifier@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" + integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== + "@babel/helper-validator-option@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" @@ -430,6 +470,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + "@babel/helper-wrap-function@^7.16.8": version "7.16.8" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz#58afda087c4cd235de92f7ceedebca2c41274200" @@ -717,6 +762,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-syntax-flow@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz#774d825256f2379d06139be0c723c4dd444f3ca1" + integrity sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" @@ -745,6 +797,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-syntax-jsx@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" + integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -1015,6 +1074,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-react-display-name@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz#8b1125f919ef36ebdfff061d664e266c666b9415" + integrity sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-react-jsx-development@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz#43a00724a3ed2557ed3f276a01a929e6686ac7b8" @@ -1022,6 +1088,24 @@ dependencies: "@babel/plugin-transform-react-jsx" "^7.16.7" +"@babel/plugin-transform-react-jsx-development@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz#dbe5c972811e49c7405b630e4d0d2e1380c0ddc5" + integrity sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.18.6" + +"@babel/plugin-transform-react-jsx@^7.14.9", "@babel/plugin-transform-react-jsx@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.18.6.tgz#2721e96d31df96e3b7ad48ff446995d26bc028ff" + integrity sha512-Mz7xMPxoy9kPS/JScj6fJs03TZ/fZ1dJPlMjRAgTaxaS0fUBk8FV/A2rRgfPsVCZqALNwMexD+0Uaf5zlcKPpw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-jsx" "^7.18.6" + "@babel/types" "^7.18.6" + "@babel/plugin-transform-react-jsx@^7.16.7": version "7.17.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz#eac1565da176ccb1a715dae0b4609858808008c1" @@ -1041,6 +1125,14 @@ "@babel/helper-annotate-as-pure" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-react-pure-annotations@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz#561af267f19f3e5d59291f9950fd7b9663d0d844" + integrity sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-regenerator@^7.17.9": version "7.17.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.17.9.tgz#0a33c3a61cf47f45ed3232903683a0afd2d3460c" @@ -1230,6 +1322,18 @@ "@babel/plugin-transform-react-jsx-development" "^7.16.7" "@babel/plugin-transform-react-pure-annotations" "^7.16.7" +"@babel/preset-react@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.18.6.tgz#979f76d6277048dc19094c217b507f3ad517dd2d" + integrity sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-transform-react-display-name" "^7.18.6" + "@babel/plugin-transform-react-jsx" "^7.18.6" + "@babel/plugin-transform-react-jsx-development" "^7.18.6" + "@babel/plugin-transform-react-pure-annotations" "^7.18.6" + "@babel/preset-typescript@^7.16.0": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz#ab114d68bb2020afc069cd51b37ff98a046a70b9" @@ -1373,6 +1477,14 @@ "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" +"@babel/types@^7.18.6": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.8.tgz#c5af199951bf41ba4a6a9a6d0d8ad722b30cd42f" + integrity sha512-qwpdsmraq0aJ3osLJRApsc2ouSJCdnMeZwB0DhbtHAtRpZNZCdlbRnHIgcRKzdE1g0iOGg644fzjOBcdOz9cPw== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1621,48 +1733,48 @@ resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.1.1.tgz#bf5d45611ab74890be386712a0e5d998c65ee2a1" integrity sha512-J/3yg2AIXc9wznaVqpHVX3Wa5jwKovVF0AMYSnbmcXTiL3PpRPfF58pzWucCwEiCJBp+hCNRLWClTomD8SseKg== -"@gemeente-denhaag/baseprops@0.2.3-alpha.158": - version "0.2.3-alpha.158" - resolved "https://registry.yarnpkg.com/@gemeente-denhaag/baseprops/-/baseprops-0.2.3-alpha.158.tgz#933c1878ebcaca5dd36debd07071503e01d151ad" - integrity sha512-S59wH06iiU5RZKHL+02WIkKnxFygHHJiH1hW3gdwlvxchzlRLXWWtYDM2VfDLemxBqM0yqqGKabwbvsbqKN5dw== +"@gemeente-denhaag/baseprops@0.2.3-alpha.257": + version "0.2.3-alpha.257" + resolved "https://registry.yarnpkg.com/@gemeente-denhaag/baseprops/-/baseprops-0.2.3-alpha.257.tgz#0f81aeb0b97716bacba9ab594fec0a9c78a87006" + integrity sha512-5w4bnkStwobSw+MnnXUVTnc0zGFDzdIioc0U66OvLEln69OhdtVkBZIDzHbkiovWi7QTmiEaJeaR+7Ra7v3Xug== -"@gemeente-denhaag/button@0.2.3-alpha.158": - version "0.2.3-alpha.158" - resolved "https://registry.yarnpkg.com/@gemeente-denhaag/button/-/button-0.2.3-alpha.158.tgz#23d05b73b6b8b56c2fa2e1248260be9bf33ce220" - integrity sha512-PX3Yl/xA6+kgIi7ZWT3l7sCb2bZ6XRgm9LpNFtqbYE4PwMf1ln8IzkmwGrT0ntBq5rfJRv4kC/v0oh1oIslakg== +"@gemeente-denhaag/button@^0.2.3-alpha.257": + version "0.2.3-alpha.257" + resolved "https://registry.yarnpkg.com/@gemeente-denhaag/button/-/button-0.2.3-alpha.257.tgz#13ef26651609c6fef66c0c9006ac3fae04617ad2" + integrity sha512-/SVdj2biABhHxo2MYPOQ5bQnAiEiRkaAGTrt3Q0niWuNYWPRUn9btd4CPYWKLK4iHlzz9JM+kc6UtIDP2c9hUA== dependencies: - "@gemeente-denhaag/baseprops" "0.2.3-alpha.158" + "@gemeente-denhaag/baseprops" "0.2.3-alpha.257" -"@gemeente-denhaag/components-css@0.1.1-alpha.166": - version "0.1.1-alpha.166" - resolved "https://registry.yarnpkg.com/@gemeente-denhaag/components-css/-/components-css-0.1.1-alpha.166.tgz#d8c12e0b0994be99a112c64f3faffb709c32dd24" - integrity sha512-CselSZ8rGN+EKxc45u4WDJ9cyIDj0EAk5YEaM5NjC1rb4kQRekWiZfKQiSFxNgVWonOqhABLQLNfdkCHlCOFKw== +"@gemeente-denhaag/components-css@0.1.1-alpha.203": + version "0.1.1-alpha.203" + resolved "https://registry.yarnpkg.com/@gemeente-denhaag/components-css/-/components-css-0.1.1-alpha.203.tgz#cb6fe4bff8b1be98930e288347183560949dfdaf" + integrity sha512-lLyghgnHRZvLXGipCnqxuNw5aNpZjMgrTDUZwv0lolLMeeh5ql+ofs8UOM5880V7ZDgJm6M/0tmjjXfXLewUHQ== -"@gemeente-denhaag/design-tokens-components@^0.2.3-alpha.227": - version "0.2.3-alpha.227" - resolved "https://registry.yarnpkg.com/@gemeente-denhaag/design-tokens-components/-/design-tokens-components-0.2.3-alpha.227.tgz#83beef454ca76503958e40a93691de47eb443934" - integrity sha512-kiz9yMhlmqSamzrctmxyds+Afed0tuBcC0PHPC/WPnoKP8w6IzHfUsCozwh3VJVEhfIOn3ATHk7wCiePLZ/hXw== +"@gemeente-denhaag/design-tokens-components@0.2.3-alpha.257": + version "0.2.3-alpha.257" + resolved "https://registry.yarnpkg.com/@gemeente-denhaag/design-tokens-components/-/design-tokens-components-0.2.3-alpha.257.tgz#5b2138778a4677411a6175393fbe7c01f4eb3ec2" + integrity sha512-X9rrD6a9SGhynONGys3vH+/Vlja0karfgXv33YukuPDZbF6ZVIm23w19AJLjl4xjhJAMpDVONafN2krfPkRQtQ== -"@gemeente-denhaag/form-progress@0.1.1-alpha.33": - version "0.1.1-alpha.33" - resolved "https://registry.yarnpkg.com/@gemeente-denhaag/form-progress/-/form-progress-0.1.1-alpha.33.tgz#ea9f00db004905595b7df8502ba79919df44e017" - integrity sha512-ULwEz5OCgBrgwAxhyqSyNKp0entdDlFxoxuJ88wcyQfBCzqfzdDaU5k6Jqd9Om4hi21ILmT96C9iq3rIM0jmQA== +"@gemeente-denhaag/form-progress@0.1.1-alpha.132": + version "0.1.1-alpha.132" + resolved "https://registry.yarnpkg.com/@gemeente-denhaag/form-progress/-/form-progress-0.1.1-alpha.132.tgz#4e8fb8f526ece49bb2d2c514f7be1492ac9e8158" + integrity sha512-f16eKiRAmxbKLGpMJhr0hBTCsnuiOwRNwaeU5+mQD1Vz+sElp9OKBG5Poplm65pLPRvD46HiHOJZ2RyDhMCL+A== dependencies: - "@gemeente-denhaag/icons" "0.2.3-alpha.158" - "@gemeente-denhaag/link" "0.2.3-alpha.158" + "@gemeente-denhaag/icons" "0.2.3-alpha.257" + "@gemeente-denhaag/link" "0.2.3-alpha.257" -"@gemeente-denhaag/icons@0.2.3-alpha.158": - version "0.2.3-alpha.158" - resolved "https://registry.yarnpkg.com/@gemeente-denhaag/icons/-/icons-0.2.3-alpha.158.tgz#1f0614b645fe94dd8ee5d536e646544d59e45c34" - integrity sha512-d171JmpU4SziJtqu54ENrLo0BTZpbEIaqvtjNZ/ArquXqucyDlIV+Oy243i0s0XG+9/Jne06/moJN72ezfDi/g== +"@gemeente-denhaag/icons@0.2.3-alpha.257", "@gemeente-denhaag/icons@^0.2.3-alpha.257": + version "0.2.3-alpha.257" + resolved "https://registry.yarnpkg.com/@gemeente-denhaag/icons/-/icons-0.2.3-alpha.257.tgz#866746e5f97c840f19da47bb314f3ed5ee8210f9" + integrity sha512-4vEDLcrQxlZcvZcTbeNN2K9mqjakmhcPgcXc1Ilt1ghdQXpqDx6RbtZf1Y+YQ2hqjlDCecjvEMZa6q4ClPCSWQ== -"@gemeente-denhaag/link@0.2.3-alpha.158": - version "0.2.3-alpha.158" - resolved "https://registry.yarnpkg.com/@gemeente-denhaag/link/-/link-0.2.3-alpha.158.tgz#0199109623d1a9abd96d2ee33a58214212f72b27" - integrity sha512-6EoCW51n0CsuZYN2FWJczEjlB3+HSVwQH2mAjzv1lCP7rsFbdfEZrA1SpNQvRmM0VbI9j63AYSxIGSLb/HvAOw== +"@gemeente-denhaag/link@0.2.3-alpha.257": + version "0.2.3-alpha.257" + resolved "https://registry.yarnpkg.com/@gemeente-denhaag/link/-/link-0.2.3-alpha.257.tgz#93bd22aa85332bc7d82dd608427131f4567ce912" + integrity sha512-BKxePlIu+S9qvCxrqNk/TeXKPnWiQXF1g/CiKDVg7gMIR5kWSXDqYHm+S9VlqaSFPo9jCfrpEkYIUiNbX2R5LA== dependencies: - "@gemeente-denhaag/baseprops" "0.2.3-alpha.158" - "@gemeente-denhaag/icons" "0.2.3-alpha.158" + "@gemeente-denhaag/baseprops" "0.2.3-alpha.257" + "@gemeente-denhaag/icons" "0.2.3-alpha.257" "@humanwhocodes/config-array@^0.9.2": version "0.9.5" @@ -1947,6 +2059,14 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== +"@jridgewell/trace-mapping@^0.3.8": + version "0.3.14" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" + integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping@^0.3.9": version "0.3.11" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.11.tgz#eb2e124521f27673493030d02dffedf60e56553f" @@ -1960,6 +2080,11 @@ resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== +"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": + version "2.1.8-no-fsevents.3" + resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b" + integrity sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ== + "@nodelib/fs.scandir@2.1.4": version "2.1.4" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" @@ -2255,6 +2380,20 @@ "@svgr/plugin-svgo" "^5.5.0" loader-utils "^2.0.0" +"@testing-library/dom@>=7.21.4": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.16.0.tgz#d6fc50250aed17b1035ca1bd64655e342db3936a" + integrity sha512-uxF4zmnLHHDlmW4l+0WDjcgLVwCvH+OVLpD8Dfp+Bjfz85prwxWGbwXgJdLtkgjD0qfOzkJF9SmA6YZPsMYX4w== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^4.2.0" + aria-query "^5.0.0" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.4.4" + pretty-format "^27.0.2" + "@testing-library/dom@^7.28.1": version "7.30.3" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.30.3.tgz#779ea9bbb92d63302461800a388a5a890ac22519" @@ -3267,6 +3406,11 @@ aria-query@^4.2.2: "@babel/runtime" "^7.10.2" "@babel/runtime-corejs3" "^7.10.2" +aria-query@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c" + integrity sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg== + array-filter@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83" @@ -3481,6 +3625,17 @@ babel-plugin-macros@^3.1.0: cosmiconfig "^7.0.0" resolve "^1.19.0" +babel-plugin-module-resolver@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-module-resolver/-/babel-plugin-module-resolver-4.1.0.tgz#22a4f32f7441727ec1fbf4967b863e1e3e9f33e2" + integrity sha512-MlX10UDheRr3lb3P0WcaIdtCSRlxdQsB1sBqL7W0raF070bGl1HQQq5K3T2vf2XAYie+ww+5AKC/WrkjRO2knA== + dependencies: + find-babel-config "^1.2.0" + glob "^7.1.6" + pkg-up "^3.1.0" + reselect "^4.0.0" + resolve "^1.13.1" + babel-plugin-named-asset-import@^0.3.8: version "0.3.8" resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz#6b7fa43c59229685368683c28bc9734f24524cc2" @@ -3884,7 +4039,7 @@ chokidar-cli@^3.0.0: lodash.throttle "^4.1.1" yargs "^13.3.0" -"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.2, chokidar@^3.5.2, chokidar@^3.5.3: +"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.0, chokidar@^3.4.2, chokidar@^3.5.2, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -4024,6 +4179,11 @@ commander@^2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + commander@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" @@ -4125,7 +4285,7 @@ contra@1.9.4: atoa "1.0.0" ticky "1.0.1" -convert-source-map@^1.4.0, convert-source-map@^1.6.0: +convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.6.0: version "1.8.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== @@ -4731,6 +4891,11 @@ dom-accessibility-api@^0.5.4: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166" integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ== +dom-accessibility-api@^0.5.9: + version "0.5.14" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz#56082f71b1dc7aac69d83c4285eef39c15d93f56" + integrity sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg== + dom-converter@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" @@ -5554,6 +5719,14 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" +find-babel-config@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-1.2.0.tgz#a9b7b317eb5b9860cda9d54740a8c8337a2283a2" + integrity sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA== + dependencies: + json5 "^0.5.1" + path-exists "^3.0.0" + find-cache-dir@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" @@ -5738,6 +5911,11 @@ fs-monkey@1.0.3: resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== +fs-readdir-recursive@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" + integrity sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA== + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -5846,27 +6024,27 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== +glob@^7.0.0, glob@^7.2.0: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.2.0: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== +glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.1.1" + minimatch "^3.0.4" once "^1.3.0" path-is-absolute "^1.0.0" @@ -6367,7 +6545,7 @@ is-callable@^1.2.4: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== -is-core-module@^2.2.0, is-core-module@^2.8.1: +is-core-module@^2.2.0, is-core-module@^2.8.1, is-core-module@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== @@ -7239,6 +7417,11 @@ json-stable-stringify@^1.0.1: dependencies: jsonify "~0.0.0" +json5@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + integrity sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw== + json5@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" @@ -7259,9 +7442,9 @@ json5@^2.2.0, json5@^2.2.1: integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== jsonc-parser@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22" - integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA== + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.1.0.tgz#73b8f0e5c940b83d03476bc2e51a20ef0932615d" + integrity sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg== jsonfile@^6.0.1: version "6.1.0" @@ -7516,6 +7699,14 @@ magic-string@^0.25.7: dependencies: sourcemap-codec "^1.4.4" +make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -7601,9 +7792,9 @@ micromatch@^4.0.4: picomatch "^2.2.3" microscope-sass@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/microscope-sass/-/microscope-sass-1.0.4.tgz#8bcb6343704643ac4f33ef6381ec6ad5a929412a" - integrity sha512-0G0VnYcd+1XHnlIVOXeXRI2tYdcRULUijQvwaPCNj4rgGK6FwuNhgHmYvhwooFehOOQ37yQ2c+pQURQ+sHoH3A== + version "1.1.0" + resolved "https://registry.yarnpkg.com/microscope-sass/-/microscope-sass-1.1.0.tgz#12ae10a6438457f51d41925c775689864a322110" + integrity sha512-eM4lwsJJSWPGiLboRxI/nWj3g2QObPCmkdozu8or5SMWiTeVLVkkSnSEbM695VYgEfCez+HBDv0KicejLoseLQ== mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": version "1.52.0" @@ -7692,11 +7883,16 @@ moment-timezone@^0.5.32: dependencies: moment ">= 2.9.0" -"moment@>= 2.9.0", moment@^2.29.1: +"moment@>= 2.9.0": version "2.29.3" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3" integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw== +moment@^2.29.1: + version "2.29.4" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" + integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -8176,6 +8372,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatc resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + pirates@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" @@ -8813,7 +9014,7 @@ pretty-format@^26.0.0, pretty-format@^26.6.2: ansi-styles "^4.0.0" react-is "^17.0.1" -pretty-format@^27.5.1: +pretty-format@^27.0.2, pretty-format@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== @@ -9363,6 +9564,11 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= +reselect@^4.0.0: + version "4.1.6" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.6.tgz#19ca2d3d0b35373a74dc1c98692cdaffb6602656" + integrity sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ== + resize-observer-polyfill@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" @@ -9415,6 +9621,15 @@ resolve@^1.12.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.2 path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +resolve@^1.13.1: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@^2.0.0-next.3: version "2.0.0-next.3" resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" @@ -9587,6 +9802,11 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== +semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" @@ -9730,6 +9950,11 @@ sisteransi@^1.0.5: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -10907,7 +11132,7 @@ which-collection@^1.0.1: which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== which-typed-array@^1.1.2: version "1.1.4"