diff --git a/package-lock.json b/package-lock.json index e7ca3b0..f9e24b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,12 +11,15 @@ "dependencies": { "@fortawesome/fontawesome-free": "^6.5.2", "@types/bootstrap": "^5.2.10", - "bootstrap": "^5.3.3" + "bootstrap": "^5.3.3", + "datatables.net-bs5": "^2.0.8" }, "devDependencies": { + "@types/jquery": "^3.5.30", "assets-webpack-plugin": "^7.1.1", "copy-webpack-plugin": "^12.0.2", "css-loader": "^7.1.2", + "css-minimizer-webpack-plugin": "^7.0.0", "mini-css-extract-plugin": "^2.9.0", "resolve-url-loader": "^5.0.0", "sass": "^1.77.5", @@ -45,6 +48,35 @@ "node": ">=6" } }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -147,6 +179,12 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, "node_modules/@sindresorhus/merge-streams": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", @@ -159,6 +197,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/@types/bootstrap": { "version": "5.2.10", "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.2.10.tgz", @@ -193,6 +240,39 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jquery": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.30.tgz", + "integrity": "sha512-nbWKkkyb919DOUxjmRVk8vwtDb0/k8FKncmUKFi+NY+QXqWltooxTrswvz4LspQwxvLdvzBN1TImr6cw3aQx2A==", + "dev": true, + "dependencies": { + "@types/sizzle": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -208,6 +288,27 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/sizzle": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", + "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, "node_modules/@webassemblyjs/ast": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", @@ -574,6 +675,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, "node_modules/bootstrap": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", @@ -654,6 +761,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001636", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz", @@ -747,6 +866,21 @@ "node": ">=6.0" } }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -779,6 +913,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true + }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -888,6 +1028,18 @@ "node": ">= 8" } }, + "node_modules/css-declaration-sorter": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", + "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, "node_modules/css-loader": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", @@ -923,6 +1075,159 @@ } } }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-7.0.0.tgz", + "integrity": "sha512-niy66jxsQHqO+EYbhPuIhqRQ1mNcNVUHrMnkzzir9kFOERJUaQDDRhh7dKDz33kBpkWMF9M8Vx0QlDbc5AHOsw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "cssnano": "^7.0.1", + "jest-worker": "^29.7.0", + "postcss": "^8.4.38", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "lightningcss": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/ajv": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -935,6 +1240,187 @@ "node": ">=4" } }, + "node_modules/cssnano": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.0.3.tgz", + "integrity": "sha512-lsekJctOTqdCn4cNrtrSwsuMR/fHC+oiVMHkp/OugBWtwjH8XJag1/OtGaYJGtz0un1fQcRy4ryfYTQsfh+KSQ==", + "dev": true, + "dependencies": { + "cssnano-preset-default": "^7.0.3", + "lilconfig": "^3.1.2" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-preset-default": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.3.tgz", + "integrity": "sha512-dQ3Ba1p/oewICp/szF1XjFFgql8OlOBrI2YNBUUwhHQnJNoMOcQTa+Bi7jSJN8r/eM1egW0Ud1se/S7qlduWKA==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.1", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^5.0.0", + "postcss-calc": "^10.0.0", + "postcss-colormin": "^7.0.1", + "postcss-convert-values": "^7.0.1", + "postcss-discard-comments": "^7.0.1", + "postcss-discard-duplicates": "^7.0.0", + "postcss-discard-empty": "^7.0.0", + "postcss-discard-overridden": "^7.0.0", + "postcss-merge-longhand": "^7.0.2", + "postcss-merge-rules": "^7.0.2", + "postcss-minify-font-values": "^7.0.0", + "postcss-minify-gradients": "^7.0.0", + "postcss-minify-params": "^7.0.1", + "postcss-minify-selectors": "^7.0.2", + "postcss-normalize-charset": "^7.0.0", + "postcss-normalize-display-values": "^7.0.0", + "postcss-normalize-positions": "^7.0.0", + "postcss-normalize-repeat-style": "^7.0.0", + "postcss-normalize-string": "^7.0.0", + "postcss-normalize-timing-functions": "^7.0.0", + "postcss-normalize-unicode": "^7.0.1", + "postcss-normalize-url": "^7.0.0", + "postcss-normalize-whitespace": "^7.0.0", + "postcss-ordered-values": "^7.0.1", + "postcss-reduce-initial": "^7.0.1", + "postcss-reduce-transforms": "^7.0.0", + "postcss-svgo": "^7.0.1", + "postcss-unique-selectors": "^7.0.1" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-utils": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.0.tgz", + "integrity": "sha512-Uij0Xdxc24L6SirFr25MlwC2rCFX6scyUmuKpzI+JQ7cyqDEwD42fJ0xfB3yLfOnRDU5LKGgjQ9FA6LYh76GWQ==", + "dev": true, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true + }, + "node_modules/datatables.net": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-2.0.8.tgz", + "integrity": "sha512-4/2dYx4vl975zQqZbyoVEm0huPe61qffjBRby7K7V+y9E+ORq4R8KavkgrNMmIgO6cl85Pg4AvCbVjvPCIT1Yg==", + "dependencies": { + "jquery": ">=1.7" + } + }, + "node_modules/datatables.net-bs5": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/datatables.net-bs5/-/datatables.net-bs5-2.0.8.tgz", + "integrity": "sha512-rpz/yO2NZMP1Uso/sSsaFAKwdCjYPa1/KLxAVr0JNJJV9ygFLHcuKTcNmoc1cekcsjYcGyybWKaNu4NfpZ74vg==", + "dependencies": { + "datatables.net": "2.0.8", + "jquery": ">=1.7" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.803", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.803.tgz", @@ -963,6 +1449,18 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/envinfo": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz", @@ -1370,6 +1868,23 @@ "node": ">=0.10.0" } }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -1384,6 +1899,11 @@ "node": ">= 10.13.0" } }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -1417,6 +1937,18 @@ "node": ">=0.10.0" } }, + "node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -1458,6 +1990,24 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -1619,6 +2169,18 @@ "node": ">=0.10.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -1749,6 +2311,206 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-calc": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.0.0.tgz", + "integrity": "sha512-OmjhudoNTP0QleZCwl1i6NeBwN+5MZbY5ersLZz69mjJiDVv/p57RjRuKDkHeDWr4T+S97wQfsqRTNoDHB2e3g==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.16", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12 || ^20.9 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.38" + } + }, + "node_modules/postcss-colormin": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.1.tgz", + "integrity": "sha512-uszdT0dULt3FQs47G5UHCduYK+FnkLYlpu1HpWu061eGsKZ7setoG7kA+WC9NQLsOJf69D5TxGHgnAdRgylnFQ==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.1", + "caniuse-api": "^3.0.0", + "colord": "^2.9.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-convert-values": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.1.tgz", + "integrity": "sha512-9x2ofb+hYPwHWMlWAzyWys2yMDZYGfkX9LodbaVTmLdlupmtH2AGvj8Up95wzzNPRDEzPIxQIkUaPJew3bT6xA==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-comments": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.1.tgz", + "integrity": "sha512-GVrQxUOhmle1W6jX2SvNLt4kmN+JYhV7mzI6BMnkAWR9DtVvg8e67rrV0NfdWhn7x1zxvzdWkMBPdBDCls+uwQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.1.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.0.tgz", + "integrity": "sha512-bAnSuBop5LpAIUmmOSsuvtKAAKREB6BBIYStWUTGq8oG5q9fClDMMuY8i4UPI/cEcDx2TN+7PMnXYIId20UVDw==", + "dev": true, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-empty": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.0.tgz", + "integrity": "sha512-e+QzoReTZ8IAwhnSdp/++7gBZ/F+nBq9y6PomfwORfP7q9nBpK5AMP64kOt0bA+lShBFbBDcgpJ3X4etHg4lzA==", + "dev": true, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.0.tgz", + "integrity": "sha512-GmNAzx88u3k2+sBTZrJSDauR0ccpE24omTQCVmaTTZFz1du6AasspjaUPMJ2ud4RslZpoFKyf+6MSPETLojc6w==", + "dev": true, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.2.tgz", + "integrity": "sha512-06vrW6ZWi9qeP7KMS9fsa9QW56+tIMW55KYqF7X3Ccn+NI2pIgPV6gFfvXTMQ05H90Y5DvnCDPZ2IuHa30PMUg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^7.0.2" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-merge-rules": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.2.tgz", + "integrity": "sha512-VAR47UNvRsdrTHLe7TV1CeEtF9SJYR5ukIB9U4GZyZOptgtsS20xSxy+k5wMrI3udST6O1XuIn7cjQkg7sDAAw==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.1", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^5.0.0", + "postcss-selector-parser": "^6.1.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.0.tgz", + "integrity": "sha512-2ckkZtgT0zG8SMc5aoNwtm5234eUx1GGFJKf2b1bSp8UflqaeFzR50lid4PfqVI9NtGqJ2J4Y7fwvnP/u1cQog==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.0.tgz", + "integrity": "sha512-pdUIIdj/C93ryCHew0UgBnL2DtUS3hfFa5XtERrs4x+hmpMYGhbzo6l/Ir5de41O0GaKVpK1ZbDNXSY6GkXvtg==", + "dev": true, + "dependencies": { + "colord": "^2.9.3", + "cssnano-utils": "^5.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-params": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.1.tgz", + "integrity": "sha512-e+Xt8xErSRPgSRFxHeBCSxMiO8B8xng7lh8E0A5ep1VfwYhY8FXhu4Q3APMjgx9YDDbSp53IBGENrzygbUvgUQ==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.1", + "cssnano-utils": "^5.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.2.tgz", + "integrity": "sha512-dCzm04wqW1uqLmDZ41XYNBJfjgps3ZugDpogAmJXoCb5oCiTzIX4oPXXKxDpTvWOnKxQKR4EbV4ZawJBLcdXXA==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "postcss-selector-parser": "^6.1.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, "node_modules/postcss-modules-extract-imports": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", @@ -1808,6 +2570,186 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-normalize-charset": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.0.tgz", + "integrity": "sha512-ABisNUXMeZeDNzCQxPxBCkXexvBrUHV+p7/BXOY+ulxkcjUZO0cp8ekGBwvIh2LbCwnWbyMPNJVtBSdyhM2zYQ==", + "dev": true, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.0.tgz", + "integrity": "sha512-lnFZzNPeDf5uGMPYgGOw7v0BfB45+irSRz9gHQStdkkhiM0gTfvWkWB5BMxpn0OqgOQuZG/mRlZyJxp0EImr2Q==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.0.tgz", + "integrity": "sha512-I0yt8wX529UKIGs2y/9Ybs2CelSvItfmvg/DBIjTnoUSrPxSV7Z0yZ8ShSVtKNaV/wAY+m7bgtyVQLhB00A1NQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.0.tgz", + "integrity": "sha512-o3uSGYH+2q30ieM3ppu9GTjSXIzOrRdCUn8UOMGNw7Af61bmurHTWI87hRybrP6xDHvOe5WlAj3XzN6vEO8jLw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-string": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.0.tgz", + "integrity": "sha512-w/qzL212DFVOpMy3UGyxrND+Kb0fvCiBBujiaONIihq7VvtC7bswjWgKQU/w4VcRyDD8gpfqUiBQ4DUOwEJ6Qg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.0.tgz", + "integrity": "sha512-tNgw3YV0LYoRwg43N3lTe3AEWZ66W7Dh7lVEpJbHoKOuHc1sLrzMLMFjP8SNULHaykzsonUEDbKedv8C+7ej6g==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.1.tgz", + "integrity": "sha512-PTPGdY9xAkTw+8ZZ71DUePb7M/Vtgkbbq+EoI33EuyQEzbKemEQMhe5QSr0VP5UfZlreANDPxSfcdSprENcbsg==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.0.tgz", + "integrity": "sha512-+d7+PpE+jyPX1hDQZYG+NaFD+Nd2ris6r8fPTBAjE8z/U41n/bib3vze8x7rKs5H1uEw5ppe9IojewouHk0klQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.0.tgz", + "integrity": "sha512-37/toN4wwZErqohedXYqWgvcHUGlT8O/m2jVkAfAe9Bd4MzRqlBmXrJRePH0e9Wgnz2X7KymTgTOaaFizQe3AQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-ordered-values": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.1.tgz", + "integrity": "sha512-irWScWRL6nRzYmBOXReIKch75RRhNS86UPUAxXdmW/l0FcAsg0lvAXQCby/1lymxn/o0gVa6Rv/0f03eJOwHxw==", + "dev": true, + "dependencies": { + "cssnano-utils": "^5.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.1.tgz", + "integrity": "sha512-0JDUSV4bGB5FGM5g8MkS+rvqKukJZ7OTHw/lcKn7xPNqeaqJyQbUO8/dJpvyTpaVwPsd3Uc33+CfNzdVowp2WA==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.1", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.0.tgz", + "integrity": "sha512-pnt1HKKZ07/idH8cpATX/ujMbtOGhUfE+m8gbqwJE05aTaNw8gbo34a2e3if0xc0dlu75sUOiqvwCGY3fzOHew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, "node_modules/postcss-selector-parser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", @@ -1821,6 +2763,37 @@ "node": ">=4" } }, + "node_modules/postcss-svgo": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.0.1.tgz", + "integrity": "sha512-0WBUlSL4lhD9rA5k1e5D8EN5wCEyZD6HJk0jIvRxl+FDVOMlJ7DePHYWGGVc5QRqrJ3/06FTXM0bxjmJpmTPSA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^3.3.2" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >= 18" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.1.tgz", + "integrity": "sha512-MH7QE/eKUftTB5ta40xcHLl7hkZjgDFydpfTK+QWXeHxghVt3VoPqYL5/G+zYZPPIs+8GuqFXSTgxBSoB1RZtQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.1.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", @@ -2180,6 +3153,22 @@ "source-map": "^0.6.0" } }, + "node_modules/stylehacks": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.2.tgz", + "integrity": "sha512-HdkWZS9b4gbgYTdMg4gJLmm7biAUug1qTqXjS+u8X+/pUd+9Px1E+520GnOW3rST9MNsVOVpsJG+mPHNosxjOQ==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.1", + "postcss-selector-parser": "^6.1.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -2207,6 +3196,40 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svgo": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "dev": true, + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", diff --git a/package.json b/package.json index ae7b9b4..f2e9f45 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,15 @@ "dependencies": { "@fortawesome/fontawesome-free": "^6.5.2", "@types/bootstrap": "^5.2.10", - "bootstrap": "^5.3.3" + "bootstrap": "^5.3.3", + "datatables.net-bs5": "^2.0.8" }, "devDependencies": { + "@types/jquery": "^3.5.30", "assets-webpack-plugin": "^7.1.1", "copy-webpack-plugin": "^12.0.2", "css-loader": "^7.1.2", + "css-minimizer-webpack-plugin": "^7.0.0", "mini-css-extract-plugin": "^2.9.0", "resolve-url-loader": "^5.0.0", "sass": "^1.77.5", diff --git a/src/file-index.json b/src/file-index.json index 7d0875a..4c8ab9b 100644 --- a/src/file-index.json +++ b/src/file-index.json @@ -1 +1 @@ -{"index":{"js":["js/runtime.js","js/index.js"],"css":"css/common.css"}} \ No newline at end of file +{"index":{"js":["js/runtime.js","js/vendors.js","js/index.js"],"css":["css/vendors.css","css/common.css"]},"admin/accounts":{"js":["js/runtime.js","js/vendors.js","js/admin/accounts.js"],"css":["css/vendors.css","css/common.css"]}} \ No newline at end of file diff --git a/src/js/pages/admin/accounts.ts b/src/js/pages/admin/accounts.ts new file mode 100644 index 0000000..6133a7b --- /dev/null +++ b/src/js/pages/admin/accounts.ts @@ -0,0 +1,102 @@ +import '../../../css/common/index.scss'; +import 'datatables.net-bs5/css/dataTables.bootstrap5.css'; +import '../../common/index'; + +import DataTable from 'datatables.net-bs5'; +import {Modal} from "bootstrap"; + +const DT_SERVERSIDE_PROCESSING_URL = '/api/dt/accounts'; +const TABLE = document.querySelector('#user-table'); + +function displayEdit(data: any) { + + const modalElem = document.querySelector('#edit-modal'); + // @ts-ignore + modalElem.querySelector('.name-input').textContent = data.name; + // @ts-ignore + modalElem.querySelector('.login-method').textContent = data.loginMethod; + // @ts-ignore + modalElem.querySelector('.permission-editor').value = data.permissionIndex; + + // @ts-ignore + modalElem?.querySelector('.js--save').dataset.userid = data.userid; + + Modal.getOrCreateInstance('#edit-modal').show(); +} + +document.addEventListener('DOMContentLoaded', () => { + const dt = new DataTable('#user-table', { + ajax: { + url: DT_SERVERSIDE_PROCESSING_URL, + }, + serverSide: true, + columns: [ + { + data: 'profilePictureUrl', + render(data, type) { + if (type !== 'display') { + return data; + } + + return `Profile Picture` + }, + orderable: false, + searchable: false + }, + { + name: 'Name', + data: 'name' + }, + { + name: 'Permission', + data: 'permission' + }, + { + name: 'Login-Method', + data: 'loginMethod' + } + ], + order: [ [1, 'asc'] ], + drawCallback: function (settings) { + const api = new DataTable.Api(settings); + api.rows().every(function (row) { + const node = this.node(); + const data = this.data(); + + node.addEventListener('click', (e) => { + displayEdit(data); + }); + }); + } + }); + + const button = document.querySelector('#edit-modal .js--save'); + if (!button) { + return; + } + + button.addEventListener('click', async (e) => { + const permissionEditor = document.querySelector('#edit-modal .permission-editor'); + if (!permissionEditor) { + return; + } + + const formData = new FormData(); + formData.set('permission', permissionEditor.value); + + const response = await fetch( + `/api/web/users/${button.dataset.userid}`, + { + method: 'POST', + body: formData + } + ); + + if (!response.ok) { + throw new Error(response.statusText); + } + + Modal.getOrCreateInstance('#edit-modal').hide(); + dt.draw(); + }) +}) \ No newline at end of file diff --git a/src/php/Login/LoginMethod.php b/src/php/Login/LoginMethod.php index 687cae6..ce4eaa0 100644 --- a/src/php/Login/LoginMethod.php +++ b/src/php/Login/LoginMethod.php @@ -13,4 +13,11 @@ enum LoginMethod: int self::DISCORD => 'fa-discord', }; } + + public function getHumanReadableName(): string + { + return match ($this) { + self::DISCORD => 'Discord', + }; + } } diff --git a/src/php/Login/UserPermission.php b/src/php/Login/UserPermission.php index 5efdc26..62413ee 100644 --- a/src/php/Login/UserPermission.php +++ b/src/php/Login/UserPermission.php @@ -5,15 +5,21 @@ namespace GamesShop\Login; enum UserPermission : int { + case NONE = 0; case VIEWER = 1; case PROVIDER = 10; case ADMIN = 100; + public function hasLevel(UserPermission $userPermission): bool { + return $this->value >= $userPermission->value; + } + public function getHumanReadableName() { return match ($this) { self::VIEWER => "Claimer", self::PROVIDER => "Provider", self::ADMIN => "Admin", + default => "None", }; } } diff --git a/src/php/Routing/AdminAccountConfigRoute.php b/src/php/Routing/AdminAccountConfigRoute.php new file mode 100644 index 0000000..7af554d --- /dev/null +++ b/src/php/Routing/AdminAccountConfigRoute.php @@ -0,0 +1,49 @@ +loginHandler->isLoggedIn()) { + throw new UnauthorizedException(); + } + + $user = $this->loginHandler->getCurrentUser(); + if ($user->getPermission()->value < UserPermission::ADMIN->value) { + throw new ForbiddenException(); + } + + + return new TemplateResponse('admin/accounts'); + } + + public static function applyRoutes(\League\Route\Router $router) { + $router->get('/accounts', self::class); + } + +} \ No newline at end of file diff --git a/src/php/Routing/Api/APIRoutes.php b/src/php/Routing/Api/APIRoutes.php new file mode 100644 index 0000000..349545e --- /dev/null +++ b/src/php/Routing/Api/APIRoutes.php @@ -0,0 +1,16 @@ +group('/api/dt', DataTablesAPIRoutes::setupRoutes(...)); + $router->group('/api/web', WebAPIRoutes::applyRoutes(...)); + } +} \ No newline at end of file diff --git a/src/php/Routing/Api/DataTables/AccountsEndpoint.php b/src/php/Routing/Api/DataTables/AccountsEndpoint.php new file mode 100644 index 0000000..8a2ed61 --- /dev/null +++ b/src/php/Routing/Api/DataTables/AccountsEndpoint.php @@ -0,0 +1,80 @@ +loginHandler->isLoggedIn()) { + throw new UnauthorizedException(); + } + + $user = $this->loginHandler->getCurrentUser(); + if (!$user->getPermission()->hasLevel(UserPermission::ADMIN)) { + throw new ForbiddenException(); + } + + + $params = $request->getQueryParams(); + $draw = $params['draw']; + $start = $params['start']; + $length = $params['length']; + + $searchValue = $params['search']['value']; + + $repo = $this->entityManager->getRepository(User::class); + $total = $repo->count(); + + $criteria = Criteria::create(); + $criteria->where(Criteria::expr()->contains('name', $searchValue)); + $criteria->setFirstResult((int)$start); + $criteria->setMaxResults((int)$length); + + $values = $repo->matching($criteria); + $filteredCount = $values->count(); + + return new JsonResponse([ + 'draw' => $draw, + 'recordsTotal' => $total, + 'recordsFiltered' => $filteredCount, + 'data' => + $values->map(function (User $user) { + return [ + 'userid' => $user->getId(), + 'name' => $user->getName(), + 'profilePictureUrl' => $user->getProfilePictureUrl(), + 'permission' => $user->getPermission()->getHumanReadableName(), + 'permissionIndex' => $user->getPermission()->value, + 'loginMethod' => $user->getLoginMethod()->getHumanReadableName(), + ]; + })->toArray() + ]); + } + + public static function applyRoutes(RouteGroup $router) { + $router->get('/accounts', AccountsEndpoint::class); + } +} \ No newline at end of file diff --git a/src/php/Routing/Api/DataTables/DataTablesAPIRoutes.php b/src/php/Routing/Api/DataTables/DataTablesAPIRoutes.php new file mode 100644 index 0000000..71110f8 --- /dev/null +++ b/src/php/Routing/Api/DataTables/DataTablesAPIRoutes.php @@ -0,0 +1,14 @@ +loginHandler->isLoggedIn()) { + throw new UnauthorizedException(); + } + + $user = $this->loginHandler->getCurrentUser(); + if (!$user->getPermission()->hasLevel(UserPermission::ADMIN)) { + throw new ForbiddenException(); + } + + $permissions = $request->getParsedBody()['permission']; + + $toChangeUser = $this->entityManager->getRepository(User::class)->find((int)$args['id']); + $toChangeUser->setPermission(UserPermission::from((int)$permissions)); + + $this->entityManager->flush(); + + return new EmptyResponse(200); + } +} \ No newline at end of file diff --git a/src/php/Routing/Api/Web/WebAPIRoutes.php b/src/php/Routing/Api/Web/WebAPIRoutes.php new file mode 100644 index 0000000..fefc155 --- /dev/null +++ b/src/php/Routing/Api/Web/WebAPIRoutes.php @@ -0,0 +1,13 @@ +post('/users/{id:number}', UserModifyRoute::class); + } +} \ No newline at end of file diff --git a/src/php/Routing/ErrorRoute.php b/src/php/Routing/ErrorRoute.php index bd8b080..736461c 100644 --- a/src/php/Routing/ErrorRoute.php +++ b/src/php/Routing/ErrorRoute.php @@ -11,7 +11,7 @@ use Psr\Http\Message\ResponseInterface; final class ErrorRoute { public function renderErrorPage(int $errorCode): ResponseInterface { - $pageContent = ContainerHandler::get(TemplateEngine::class)->renderPage('error', [ 'errorCode' => $errorCode ]); + $pageContent = ContainerHandler::get(TemplateEngine::class)->renderErrorPage($errorCode); $response = new Response; $response->getBody()->write($pageContent); diff --git a/src/php/Routing/LoginRoutes.php b/src/php/Routing/LoginRoutes.php index e3d11bf..4e3ef4c 100644 --- a/src/php/Routing/LoginRoutes.php +++ b/src/php/Routing/LoginRoutes.php @@ -9,6 +9,7 @@ use Doctrine\ORM\OptimisticLockException; use GamesShop\ContainerHandler; use GamesShop\Environment\EnvironmentHandler; use GamesShop\Login\LoginHandler; +use GamesShop\Routing\Responses\TemplateResponse; use GamesShop\Templates\TemplateEngine; use Laminas\Diactoros\Response; use Psr\Http\Message\ResponseInterface; @@ -24,16 +25,10 @@ final class LoginRoutes public function login(ServerRequestInterface $request) { $discordEnv = ContainerHandler::get(EnvironmentHandler::class)->getDiscordEnvironment(); - $pageContent = ContainerHandler::get(TemplateEngine::class)->renderPage( - 'login', - [ - 'discordUrl' => $discordEnv->loginUrl - ] - ); - $response = new Response; - $response->getBody()->write($pageContent); - return $response; + return new TemplateResponse('login', [ + 'discordUrl' => $discordEnv->loginUrl + ]); } /** diff --git a/src/php/Routing/ResourceRoute.php b/src/php/Routing/ResourceRoute.php index 0a269b9..03fcf79 100644 --- a/src/php/Routing/ResourceRoute.php +++ b/src/php/Routing/ResourceRoute.php @@ -6,9 +6,9 @@ namespace GamesShop\Routing; use GamesShop\ContainerHandler; use GamesShop\Paths; use Laminas\Diactoros\Response; +use Laminas\Diactoros\Uri; use Mimey\MimeTypes; use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; final class ResourceRoute { @@ -19,9 +19,20 @@ final class ResourceRoute 'gif', 'svg', 'png', 'jpg' ]; - public function __invoke(ServerRequestInterface $request, array $args): ResponseInterface { + public function isValid(Uri $uri): bool { + $path = $uri->getPath(); + foreach (self::RESOURCE_EXTENSIONS as $extension) { + if (!str_ends_with($path, $extension)) { + continue; + } - $filePath = Paths::PUBLIC_PATH . $request->getUri()->getPath(); + return true; + } + return false; + } + + public function getResponse(Uri $uri): ResponseInterface { + $filePath = Paths::PUBLIC_PATH . $uri->getPath(); if (!file_exists($filePath)) { $response = new Response(status: 404); @@ -41,11 +52,4 @@ final class ResourceRoute return $response; } - - public static function addRouteEntry(\League\Route\Router $router): void { - $joinedResourceExtensions = implode('|', self::RESOURCE_EXTENSIONS); - $router->addPatternMatcher('resource', ".+[{$joinedResourceExtensions}]"); - - $router->get('/{resource:resource}', self::class); - } } \ No newline at end of file diff --git a/src/php/Routing/Responses/TemplateResponse.php b/src/php/Routing/Responses/TemplateResponse.php new file mode 100644 index 0000000..4731539 --- /dev/null +++ b/src/php/Routing/Responses/TemplateResponse.php @@ -0,0 +1,20 @@ +renderPage($templateName, $data); + $this->getBody()->write($body); + } +} \ No newline at end of file diff --git a/src/php/Routing/Router.php b/src/php/Routing/Router.php index 9f0a98d..ce32605 100644 --- a/src/php/Routing/Router.php +++ b/src/php/Routing/Router.php @@ -5,22 +5,36 @@ namespace GamesShop\Routing; use GamesShop\ContainerHandler; use GamesShop\Login\LoginHandler; +use GamesShop\Routing\Api\APIRoutes; use GamesShop\Templates\TemplateEngine; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequestFactory; use League\Container\Container; +use League\Route\Http\Exception\BadRequestException; +use League\Route\Http\Exception\ForbiddenException; use League\Route\Http\Exception\NotFoundException; +use League\Route\Http\Exception\UnauthorizedException; use League\Route\Strategy\ApplicationStrategy; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; final class Router { + public function __construct( + private ResourceRoute $resourceRoute + ) + { + } + public function route(): ResponseInterface { $request = ServerRequestFactory::fromGlobals( $_SERVER, $_GET, $_POST, $_COOKIE, $_FILES ); + if ($this->resourceRoute->isValid($request->getUri())) { + return $this->resourceRoute->getResponse($request->getUri()); + } + $router = new \League\Route\Router; $strategy = (new ApplicationStrategy)->setContainer(ContainerHandler::getInstance()); $router->setStrategy($strategy); @@ -28,12 +42,18 @@ final class Router IndexRoute::applyRoutes($router); LoginRoutes::addRoutes($router); SetupRoute::applyRoutes($router); - ResourceRoute::addRouteEntry($router); + AdminAccountConfigRoute::applyRoutes($router); + + APIRoutes::applyRoutes($router); try { return $router->dispatch($request); } catch (NotFoundException $e) { return (new ErrorRoute())->renderErrorPage(404); + } catch (UnauthorizedException) { + return (new ErrorRoute())->renderErrorPage(401); + } catch (ForbiddenException) { + return (new ErrorRoute())->renderErrorPage(403); } } } \ No newline at end of file diff --git a/src/php/Routing/SetupRoute.php b/src/php/Routing/SetupRoute.php index b351ddf..0334b39 100644 --- a/src/php/Routing/SetupRoute.php +++ b/src/php/Routing/SetupRoute.php @@ -29,7 +29,6 @@ final class SetupRoute $attribute = $repo->find('ADMIN_SETUP_COMPLETED'); if ($attribute) { - return new RedirectResponse('/'); } diff --git a/src/php/Templates/NavigationHeader.php b/src/php/Templates/NavigationHeader.php new file mode 100644 index 0000000..1e8c496 --- /dev/null +++ b/src/php/Templates/NavigationHeader.php @@ -0,0 +1,17 @@ +addData([ 'resources' => $this->resourceIndex, + 'activeUser' => $loginHandler->isLoggedIn() ? $loginHandler->getCurrentUser() : null, ]); } @@ -24,4 +27,8 @@ final class TemplateEngine extends Engine { return parent::render("pages/$page", $data); } + + public function renderErrorPage(int $error) { + return self::renderPage('error', [ 'errorCode' => $error ]); + } } \ No newline at end of file diff --git a/src/templates/layout/accountDisplay.php b/src/templates/layout/accountDisplay.php index cd42d32..a3a7760 100644 --- a/src/templates/layout/accountDisplay.php +++ b/src/templates/layout/accountDisplay.php @@ -1,31 +1,29 @@ -isLoggedIn()): - $user = $loginHandler->getCurrentUser(); -?> +
- User Profile Picture + User Profile Picture
- getName() ?> + getName() ?> - getPermission()->getHumanReadableName() ?> + getPermission()->getHumanReadableName() ?>
diff --git a/src/templates/layout/main.php b/src/templates/layout/main.php index 6609dfd..472b100 100644 --- a/src/templates/layout/main.php +++ b/src/templates/layout/main.php @@ -39,5 +39,7 @@ $resource = $resources->getResource($resourceEntry); section('content'); ?> + section('modal') ?> + diff --git a/src/templates/layout/navbar.php b/src/templates/layout/navbar.php index cf37a69..afc01c9 100644 --- a/src/templates/layout/navbar.php +++ b/src/templates/layout/navbar.php @@ -1,10 +1,17 @@ getPermission(); ?> @@ -15,7 +22,19 @@ ContainerHandler::get(LoginHandler::class); diff --git a/src/templates/pages/admin/accounts.php b/src/templates/pages/admin/accounts.php new file mode 100644 index 0000000..b412b32 --- /dev/null +++ b/src/templates/pages/admin/accounts.php @@ -0,0 +1,57 @@ +layout('layout/main', [ 'resourceEntry' => 'admin/accounts' ]); +?> + +

Users

+ + + + + + + + + + + +
NamePermissionLogin-Method
+ +start('modal') ?> + +end() ?> diff --git a/webpack.config.js b/webpack.config.js index 34dd30f..fa8251c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,6 +2,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin'), Path = require('path'), AssetsPlugin = require('assets-webpack-plugin'), CopyPlugin = require('copy-webpack-plugin'); +const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); const PUBLIC_FOLDER = Path.resolve(__dirname, 'public'), SOURCE_FOLDER = Path.resolve(__dirname, 'src'), @@ -38,6 +39,10 @@ module.exports = { devtool: 'source-map', optimization: { runtimeChunk: "single", + minimize: true, + minimizer: [ + new CssMinimizerPlugin() + ], splitChunks: { cacheGroups: { vendor: { @@ -86,7 +91,8 @@ module.exports = { extensions: ['.js', '.ts'], }, entry: { - index: JS_FOLDER + "/pages/index" + index: JS_FOLDER + "/pages/index", + 'admin/accounts': JS_FOLDER + "/pages/admin/accounts", }, output: { path: PUBLIC_FOLDER,