diff --git a/.eslintrc.json b/.eslintrc.json
deleted file mode 100644
index db226e9..0000000
--- a/.eslintrc.json
+++ /dev/null
@@ -1,47 +0,0 @@
-{
- "root": true,
- "ignorePatterns": [
- "projects/**/*"
- ],
- "overrides": [
- {
- "files": [
- "*.ts"
- ],
- "extends": [
- "eslint:recommended",
- "plugin:@typescript-eslint/recommended",
- "plugin:@angular-eslint/recommended",
- "plugin:@angular-eslint/template/process-inline-templates"
- ],
- "rules": {
- "@angular-eslint/directive-selector": [
- "error",
- {
- "type": "attribute",
- "prefix": "app",
- "style": "camelCase"
- }
- ],
- "@angular-eslint/component-selector": [
- "error",
- {
- "type": "element",
- "prefix": "app",
- "style": "kebab-case"
- }
- ]
- }
- },
- {
- "files": [
- "*.html"
- ],
- "extends": [
- "plugin:@angular-eslint/template/recommended",
- "plugin:@angular-eslint/template/accessibility"
- ],
- "rules": {}
- }
- ]
-}
diff --git a/angular.json b/angular.json
index b5db2fb..08bed92 100644
--- a/angular.json
+++ b/angular.json
@@ -31,6 +31,7 @@
"src/assets"
],
"styles": [
+ "@angular/material/prebuilt-themes/azure-blue.css",
"src/styles.scss"
],
"scripts": [],
@@ -93,6 +94,7 @@
"src/assets"
],
"styles": [
+ "@angular/material/prebuilt-themes/azure-blue.css",
"src/styles.scss"
],
"scripts": []
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 0000000..87cf8da
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,61 @@
+import { FlatCompat } from "@eslint/eslintrc";
+import js from "@eslint/js";
+import { defineConfig, globalIgnores } from "eslint/config";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const compat = new FlatCompat({
+ baseDirectory: __dirname,
+ recommendedConfig: js.configs.recommended,
+ allConfig: js.configs.all,
+});
+
+export default defineConfig([
+ globalIgnores(["projects/**/*", ".angular"]),
+ {
+ files: ["**/*.ts"],
+
+ extends: compat.extends(
+ "eslint:recommended",
+ "plugin:@typescript-eslint/recommended",
+ "plugin:@angular-eslint/recommended",
+ "plugin:@angular-eslint/template/process-inline-templates",
+ ),
+
+ rules: {
+ "@angular-eslint/directive-selector": [
+ "error",
+ {
+ type: "attribute",
+ prefix: "app",
+ style: "camelCase",
+ },
+ ],
+
+ "@angular-eslint/component-selector": [
+ "error",
+ {
+ type: "element",
+ prefix: "app",
+ style: "kebab-case",
+ },
+ ],
+ },
+ },
+ {
+ files: ["**/*.html"],
+
+ extends: compat.extends(
+ "plugin:@angular-eslint/template/recommended",
+ "plugin:@angular-eslint/template/accessibility",
+ ),
+
+ rules: {},
+ },
+ {
+ files: ["**/*.ts"],
+ extends: compat.extends("plugin:@ngrx/all"),
+ },
+]);
diff --git a/package-lock.json b/package-lock.json
index 5e4c46a..ab54af8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,38 +8,50 @@
"name": "angular-template",
"version": "0.0.0",
"dependencies": {
- "@angular/animations": "^19.2.10",
- "@angular/common": "^19.2.10",
- "@angular/compiler": "^19.2.10",
- "@angular/core": "^19.2.10",
- "@angular/forms": "^19.2.10",
- "@angular/platform-browser": "^19.2.10",
- "@angular/platform-browser-dynamic": "^19.2.10",
- "@angular/router": "^19.2.10",
- "rxjs": "~7.8.0",
+ "@angular/animations": "^19.2.12",
+ "@angular/cdk": "^19.2.17",
+ "@angular/common": "^19.2.12",
+ "@angular/compiler": "^19.2.12",
+ "@angular/core": "^19.2.12",
+ "@angular/forms": "^19.2.12",
+ "@angular/material": "^19.2.17",
+ "@angular/platform-browser": "^19.2.12",
+ "@angular/platform-browser-dynamic": "^19.2.12",
+ "@angular/router": "^19.2.12",
+ "@ngrx/effects": "^19.2.0",
+ "@ngrx/entity": "^19.2.0",
+ "@ngrx/operators": "^19.2.0",
+ "@ngrx/router-store": "^19.2.0",
+ "@ngrx/store": "^19.2.0",
+ "@ngrx/store-devtools": "^19.2.0",
+ "rxjs": "^7.8.2",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
},
"devDependencies": {
- "@angular-devkit/build-angular": "^19.2.12",
+ "@angular-devkit/build-angular": "^19.2.13",
+ "@angular-devkit/core": "^19.2.13",
"@angular-eslint/builder": "19.4.0",
"@angular-eslint/eslint-plugin": "19.4.0",
"@angular-eslint/eslint-plugin-template": "19.4.0",
"@angular-eslint/schematics": "19.4.0",
"@angular-eslint/template-parser": "19.4.0",
- "@angular/cli": "^19.2.12",
- "@angular/compiler-cli": "^19.2.10",
- "@types/jasmine": "~4.3.0",
+ "@angular/cli": "^19.2.13",
+ "@angular/compiler-cli": "^19.2.12",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "^9.27.0",
+ "@ngrx/eslint-plugin": "^19.2.0",
+ "@types/jasmine": "^5.1.8",
"@typescript-eslint/eslint-plugin": "8.32.1",
"@typescript-eslint/parser": "8.32.1",
- "eslint": "^8.51.0",
- "jasmine-core": "~4.6.0",
+ "eslint": "^9.27.0",
+ "jasmine-core": "^5.7.1",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
- "prettier": "^3.3.3",
+ "prettier": "^3.5.3",
"typescript": "~5.8.3"
}
},
@@ -58,13 +70,13 @@
}
},
"node_modules/@angular-devkit/architect": {
- "version": "0.1902.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.12.tgz",
- "integrity": "sha512-LfUc7k84YL290hAxsG+FvjQpXugQXyw5aDzrQQB4iTYhBgaABu2aaNOU4eu3JH+F8NeXd2EBF/YMr2LDSkYlMw==",
+ "version": "0.1902.13",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.13.tgz",
+ "integrity": "sha512-ZMj+PjK22Ph2U8usG6L7LqEfvWlbaOvmiWXSrEt9YiC9QJt6rsumCkOgUIsmHQtucm/lK+9CMtyYdwH2fYycjg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@angular-devkit/core": "19.2.12",
+ "@angular-devkit/core": "19.2.13",
"rxjs": "7.8.1"
},
"engines": {
@@ -73,111 +85,28 @@
"yarn": ">= 1.13.0"
}
},
- "node_modules/@angular-devkit/architect/node_modules/@angular-devkit/core": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.12.tgz",
- "integrity": "sha512-v5pdfZHZ8MTZozfpkhKoPFBpXQW+2GFbTfdyis8FBtevJWCbIsCR3xhodgI4jwzkSEAraN4oVtWvSytdNyBC6A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "8.17.1",
- "ajv-formats": "3.0.1",
- "jsonc-parser": "3.3.1",
- "picomatch": "4.0.2",
- "rxjs": "7.8.1",
- "source-map": "0.7.4"
- },
- "engines": {
- "node": "^18.19.1 || ^20.11.1 || >=22.0.0",
- "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
- "yarn": ">= 1.13.0"
- },
- "peerDependencies": {
- "chokidar": "^4.0.0"
- },
- "peerDependenciesMeta": {
- "chokidar": {
- "optional": true
- }
- }
- },
- "node_modules/@angular-devkit/architect/node_modules/ajv-formats": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
- "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependenciesMeta": {
- "ajv": {
- "optional": true
- }
- }
- },
- "node_modules/@angular-devkit/architect/node_modules/chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "node_modules/@angular-devkit/architect/node_modules/rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
+ "license": "Apache-2.0",
"dependencies": {
- "readdirp": "^4.0.1"
- },
- "engines": {
- "node": ">= 14.16.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/@angular-devkit/architect/node_modules/picomatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
- "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/@angular-devkit/architect/node_modules/readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">= 14.18.0"
- },
- "funding": {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
+ "tslib": "^2.1.0"
}
},
"node_modules/@angular-devkit/build-angular": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-19.2.12.tgz",
- "integrity": "sha512-gPx3Vi7QFzHkSV388en6VqSqasojitJKuKmgTMPOV5keLtpOylPv3rjnr8oO9rYbYmLsT/WTUsP7bYiZhrr19Q==",
+ "version": "19.2.13",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-19.2.13.tgz",
+ "integrity": "sha512-MrNpwrCq6COszhxyD/u2LE0yygTEjIAlaKaIvvDi9nurzUoKRc1vIJWeB2VkGgmUEjj6OTEeM/6zbo02s88EzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "2.3.0",
- "@angular-devkit/architect": "0.1902.12",
- "@angular-devkit/build-webpack": "0.1902.12",
- "@angular-devkit/core": "19.2.12",
- "@angular/build": "19.2.12",
+ "@angular-devkit/architect": "0.1902.13",
+ "@angular-devkit/build-webpack": "0.1902.13",
+ "@angular-devkit/core": "19.2.13",
+ "@angular/build": "19.2.13",
"@babel/core": "7.26.10",
"@babel/generator": "7.26.10",
"@babel/helper-annotate-as-pure": "7.25.9",
@@ -188,7 +117,7 @@
"@babel/preset-env": "7.26.9",
"@babel/runtime": "7.26.10",
"@discoveryjs/json-ext": "0.6.3",
- "@ngtools/webpack": "19.2.12",
+ "@ngtools/webpack": "19.2.13",
"@vitejs/plugin-basic-ssl": "1.2.0",
"ansi-colors": "4.1.3",
"autoprefixer": "10.4.20",
@@ -242,7 +171,7 @@
"@angular/localize": "^19.0.0 || ^19.2.0-next.0",
"@angular/platform-server": "^19.0.0 || ^19.2.0-next.0",
"@angular/service-worker": "^19.0.0 || ^19.2.0-next.0",
- "@angular/ssr": "^19.2.12",
+ "@angular/ssr": "^19.2.13",
"@web/test-runner": "^0.20.0",
"browser-sync": "^3.0.2",
"jest": "^29.5.0",
@@ -292,34 +221,6 @@
}
}
},
- "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.12.tgz",
- "integrity": "sha512-v5pdfZHZ8MTZozfpkhKoPFBpXQW+2GFbTfdyis8FBtevJWCbIsCR3xhodgI4jwzkSEAraN4oVtWvSytdNyBC6A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "8.17.1",
- "ajv-formats": "3.0.1",
- "jsonc-parser": "3.3.1",
- "picomatch": "4.0.2",
- "rxjs": "7.8.1",
- "source-map": "0.7.4"
- },
- "engines": {
- "node": "^18.19.1 || ^20.11.1 || >=22.0.0",
- "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
- "yarn": ">= 1.13.0"
- },
- "peerDependencies": {
- "chokidar": "^4.0.0"
- },
- "peerDependenciesMeta": {
- "chokidar": {
- "optional": true
- }
- }
- },
"node_modules/@angular-devkit/build-angular/node_modules/@babel/core": {
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz",
@@ -361,42 +262,6 @@
"semver": "bin/semver.js"
}
},
- "node_modules/@angular-devkit/build-angular/node_modules/ajv-formats": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
- "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependenciesMeta": {
- "ajv": {
- "optional": true
- }
- }
- },
- "node_modules/@angular-devkit/build-angular/node_modules/chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "readdirp": "^4.0.1"
- },
- "engines": {
- "node": ">= 14.16.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
"node_modules/@angular-devkit/build-angular/node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -463,20 +328,14 @@
"node": "^10 || ^12 || >=14"
}
},
- "node_modules/@angular-devkit/build-angular/node_modules/readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "node_modules/@angular-devkit/build-angular/node_modules/rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">= 14.18.0"
- },
- "funding": {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/semver": {
@@ -493,13 +352,13 @@
}
},
"node_modules/@angular-devkit/build-webpack": {
- "version": "0.1902.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1902.12.tgz",
- "integrity": "sha512-JNwvzaN2RVbG1IClFPXhNpysVwf55nWmVsNN5iQHRXkD3kpqnaOfhUBtlhBBjLf/i6cwKEne2TI8zciaEYr+iw==",
+ "version": "0.1902.13",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1902.13.tgz",
+ "integrity": "sha512-upb+cKWkuXwmKyppSwZf3ryHWPm4aS6sJkQu0TWh4RoMRp1WCYVxUfgZ28fTMqcBF3eoFy2XPjdOfkJDRb6Hrg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@angular-devkit/architect": "0.1902.12",
+ "@angular-devkit/architect": "0.1902.13",
"rxjs": "7.8.1"
},
"engines": {
@@ -512,29 +371,20 @@
"webpack-dev-server": "^5.0.2"
}
},
- "node_modules/@angular-devkit/schematics": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.12.tgz",
- "integrity": "sha512-vK5NI/asi1snWFkw02DpmC8tLq6u5ZbUwwXxgALKuVwGl3g1VLzrHrkoSCrcsOO9Nu6GQOPbxax2lR/DICmytg==",
+ "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"dev": true,
- "license": "MIT",
+ "license": "Apache-2.0",
"dependencies": {
- "@angular-devkit/core": "19.2.12",
- "jsonc-parser": "3.3.1",
- "magic-string": "0.30.17",
- "ora": "5.4.1",
- "rxjs": "7.8.1"
- },
- "engines": {
- "node": "^18.19.1 || ^20.11.1 || >=22.0.0",
- "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
- "yarn": ">= 1.13.0"
+ "tslib": "^2.1.0"
}
},
- "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.12.tgz",
- "integrity": "sha512-v5pdfZHZ8MTZozfpkhKoPFBpXQW+2GFbTfdyis8FBtevJWCbIsCR3xhodgI4jwzkSEAraN4oVtWvSytdNyBC6A==",
+ "node_modules/@angular-devkit/core": {
+ "version": "19.2.13",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.13.tgz",
+ "integrity": "sha512-iq73hE5Uvms1w3uMUSk4i4NDXDMQ863VAifX8LOTadhG6U0xISjNJ11763egVCxQmaKmg7zbG4rda88wHJATzA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -559,7 +409,7 @@
}
}
},
- "node_modules/@angular-devkit/schematics/node_modules/ajv-formats": {
+ "node_modules/@angular-devkit/core/node_modules/ajv-formats": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
"integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
@@ -577,25 +427,7 @@
}
}
},
- "node_modules/@angular-devkit/schematics/node_modules/chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "readdirp": "^4.0.1"
- },
- "engines": {
- "node": ">= 14.16.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/@angular-devkit/schematics/node_modules/picomatch": {
+ "node_modules/@angular-devkit/core/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
@@ -608,128 +440,58 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/@angular-devkit/schematics/node_modules/readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">= 14.18.0"
- },
- "funding": {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/@angular-eslint/builder": {
- "version": "19.4.0",
- "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-19.4.0.tgz",
- "integrity": "sha512-+mI/YwXiT+IPRNuTNrQqOm97iqbPM5Zc4fxR3p9N1xj1dxLyXJrtGXbbWJPK6i74ON7KdpkY3WLhMKnhWM4RzQ==",
+ "node_modules/@angular-devkit/core/node_modules/rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"dev": true,
- "license": "MIT",
+ "license": "Apache-2.0",
"dependencies": {
- "@angular-devkit/architect": ">= 0.1900.0 < 0.2000.0",
- "@angular-devkit/core": ">= 19.0.0 < 20.0.0"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": "*"
+ "tslib": "^2.1.0"
}
},
- "node_modules/@angular-eslint/builder/node_modules/@angular-devkit/core": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.12.tgz",
- "integrity": "sha512-v5pdfZHZ8MTZozfpkhKoPFBpXQW+2GFbTfdyis8FBtevJWCbIsCR3xhodgI4jwzkSEAraN4oVtWvSytdNyBC6A==",
+ "node_modules/@angular-devkit/schematics": {
+ "version": "19.2.13",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.13.tgz",
+ "integrity": "sha512-NhSPz3lI9njEo8eMUlZVGtlXl12UcNZv5lWTBZY/FGWUu6P5ciD/9iJINbc1jiaDH5E/DLEicUNuai0Q91X4Nw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ajv": "8.17.1",
- "ajv-formats": "3.0.1",
+ "@angular-devkit/core": "19.2.13",
"jsonc-parser": "3.3.1",
- "picomatch": "4.0.2",
- "rxjs": "7.8.1",
- "source-map": "0.7.4"
+ "magic-string": "0.30.17",
+ "ora": "5.4.1",
+ "rxjs": "7.8.1"
},
"engines": {
"node": "^18.19.1 || ^20.11.1 || >=22.0.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
- },
- "peerDependencies": {
- "chokidar": "^4.0.0"
- },
- "peerDependenciesMeta": {
- "chokidar": {
- "optional": true
- }
}
},
- "node_modules/@angular-eslint/builder/node_modules/ajv-formats": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
- "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+ "node_modules/@angular-devkit/schematics/node_modules/rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"dev": true,
- "license": "MIT",
+ "license": "Apache-2.0",
"dependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependenciesMeta": {
- "ajv": {
- "optional": true
- }
+ "tslib": "^2.1.0"
}
},
- "node_modules/@angular-eslint/builder/node_modules/chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "node_modules/@angular-eslint/builder": {
+ "version": "19.4.0",
+ "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-19.4.0.tgz",
+ "integrity": "sha512-+mI/YwXiT+IPRNuTNrQqOm97iqbPM5Zc4fxR3p9N1xj1dxLyXJrtGXbbWJPK6i74ON7KdpkY3WLhMKnhWM4RzQ==",
"dev": true,
"license": "MIT",
- "optional": true,
- "peer": true,
"dependencies": {
- "readdirp": "^4.0.1"
- },
- "engines": {
- "node": ">= 14.16.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/@angular-eslint/builder/node_modules/picomatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
- "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/@angular-eslint/builder/node_modules/readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">= 14.18.0"
+ "@angular-devkit/architect": ">= 0.1900.0 < 0.2000.0",
+ "@angular-devkit/core": ">= 19.0.0 < 20.0.0"
},
- "funding": {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": "*"
}
},
"node_modules/@angular-eslint/bundled-angular-compiler": {
@@ -813,75 +575,11 @@
"dependencies": {
"@angular-devkit/core": ">= 19.0.0 < 20.0.0",
"@angular-devkit/schematics": ">= 19.0.0 < 20.0.0",
- "@angular-eslint/eslint-plugin": "19.4.0",
- "@angular-eslint/eslint-plugin-template": "19.4.0",
- "ignore": "7.0.4",
- "semver": "7.7.1",
- "strip-json-comments": "3.1.1"
- }
- },
- "node_modules/@angular-eslint/schematics/node_modules/@angular-devkit/core": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.12.tgz",
- "integrity": "sha512-v5pdfZHZ8MTZozfpkhKoPFBpXQW+2GFbTfdyis8FBtevJWCbIsCR3xhodgI4jwzkSEAraN4oVtWvSytdNyBC6A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "8.17.1",
- "ajv-formats": "3.0.1",
- "jsonc-parser": "3.3.1",
- "picomatch": "4.0.2",
- "rxjs": "7.8.1",
- "source-map": "0.7.4"
- },
- "engines": {
- "node": "^18.19.1 || ^20.11.1 || >=22.0.0",
- "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
- "yarn": ">= 1.13.0"
- },
- "peerDependencies": {
- "chokidar": "^4.0.0"
- },
- "peerDependenciesMeta": {
- "chokidar": {
- "optional": true
- }
- }
- },
- "node_modules/@angular-eslint/schematics/node_modules/ajv-formats": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
- "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependenciesMeta": {
- "ajv": {
- "optional": true
- }
- }
- },
- "node_modules/@angular-eslint/schematics/node_modules/chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "readdirp": "^4.0.1"
- },
- "engines": {
- "node": ">= 14.16.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
+ "@angular-eslint/eslint-plugin": "19.4.0",
+ "@angular-eslint/eslint-plugin-template": "19.4.0",
+ "ignore": "7.0.4",
+ "semver": "7.7.1",
+ "strip-json-comments": "3.1.1"
}
},
"node_modules/@angular-eslint/schematics/node_modules/ignore": {
@@ -894,35 +592,6 @@
"node": ">= 4"
}
},
- "node_modules/@angular-eslint/schematics/node_modules/picomatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
- "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/@angular-eslint/schematics/node_modules/readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">= 14.18.0"
- },
- "funding": {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
- }
- },
"node_modules/@angular-eslint/schematics/node_modules/semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
@@ -969,9 +638,9 @@
}
},
"node_modules/@angular/animations": {
- "version": "19.2.10",
- "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-19.2.10.tgz",
- "integrity": "sha512-LlH6mt0D8+MI0LV8QNx9/rqLLv0WCfYPS/5iXSGpIyfsflLoO5dRGhtS4pdQbu5gqmnLdf9i7r4ldRJjf7wb+g==",
+ "version": "19.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-19.2.12.tgz",
+ "integrity": "sha512-PEONFwzhUzeAMFW2ehfL+uGnkeVsikoEOJ3RJVdHWH7W8GUgdN2ubogw6umu939/MqL1MsItImXQcBA7aWfzSg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -980,19 +649,19 @@
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
- "@angular/common": "19.2.10",
- "@angular/core": "19.2.10"
+ "@angular/common": "19.2.12",
+ "@angular/core": "19.2.12"
}
},
"node_modules/@angular/build": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular/build/-/build-19.2.12.tgz",
- "integrity": "sha512-G28ux1T5QDlWporwupWbcodBN3rcyHfK2Dh5M3UC5hj0GstpfEHcpBHxawZzIxhqPKy//tdVLlzORUgvAwnqbA==",
+ "version": "19.2.13",
+ "resolved": "https://registry.npmjs.org/@angular/build/-/build-19.2.13.tgz",
+ "integrity": "sha512-ABcwhAB9DpsvXY7joRFSKiQCHJmCokVJK1Liuz0/AI9Xlp7spqaWqJcC1DVWO0645tUk4HhYmUh5a68REK1Q1A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "2.3.0",
- "@angular-devkit/architect": "0.1902.12",
+ "@angular-devkit/architect": "0.1902.13",
"@babel/core": "7.26.10",
"@babel/helper-annotate-as-pure": "7.25.9",
"@babel/helper-split-export-declaration": "7.24.7",
@@ -1032,7 +701,7 @@
"@angular/localize": "^19.0.0 || ^19.2.0-next.0",
"@angular/platform-server": "^19.0.0 || ^19.2.0-next.0",
"@angular/service-worker": "^19.0.0 || ^19.2.0-next.0",
- "@angular/ssr": "^19.2.12",
+ "@angular/ssr": "^19.2.13",
"karma": "^6.4.0",
"less": "^4.2.0",
"ng-packagr": "^19.0.0 || ^19.2.0-next.0",
@@ -1249,19 +918,34 @@
}
}
},
+ "node_modules/@angular/cdk": {
+ "version": "19.2.17",
+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.2.17.tgz",
+ "integrity": "sha512-3jG33S+5+kqymCRwQlcSEWlY5rYwkKxe0onln+NXxT0/kteR02vWvv1+Li4/QqSr5JvsGHEhAFsZaR9QtOzbdA==",
+ "license": "MIT",
+ "dependencies": {
+ "parse5": "^7.1.2",
+ "tslib": "^2.3.0"
+ },
+ "peerDependencies": {
+ "@angular/common": "^19.0.0 || ^20.0.0",
+ "@angular/core": "^19.0.0 || ^20.0.0",
+ "rxjs": "^6.5.3 || ^7.4.0"
+ }
+ },
"node_modules/@angular/cli": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.2.12.tgz",
- "integrity": "sha512-cZkHpM16uh3VouHG1XdWSk0ZWisQRxMVADk5IJlM9jMcPqnFyJwD/UXCS+XTaW3POpNDwsmbh2UB9Xabdgo7rw==",
+ "version": "19.2.13",
+ "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.2.13.tgz",
+ "integrity": "sha512-dDRCS73/lrItWx9j4SmwHR56GiZsW8ObNi2q9l/1ny813CG9K43STYFG/wJvGS7ZF3y5hvjIiJOwBx2YIouOIw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@angular-devkit/architect": "0.1902.12",
- "@angular-devkit/core": "19.2.12",
- "@angular-devkit/schematics": "19.2.12",
+ "@angular-devkit/architect": "0.1902.13",
+ "@angular-devkit/core": "19.2.13",
+ "@angular-devkit/schematics": "19.2.13",
"@inquirer/prompts": "7.3.2",
"@listr2/prompt-adapter-inquirer": "2.0.18",
- "@schematics/angular": "19.2.12",
+ "@schematics/angular": "19.2.13",
"@yarnpkg/lockfile": "1.1.0",
"ini": "5.0.0",
"jsonc-parser": "3.3.1",
@@ -1283,99 +967,6 @@
"yarn": ">= 1.13.0"
}
},
- "node_modules/@angular/cli/node_modules/@angular-devkit/core": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.12.tgz",
- "integrity": "sha512-v5pdfZHZ8MTZozfpkhKoPFBpXQW+2GFbTfdyis8FBtevJWCbIsCR3xhodgI4jwzkSEAraN4oVtWvSytdNyBC6A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "8.17.1",
- "ajv-formats": "3.0.1",
- "jsonc-parser": "3.3.1",
- "picomatch": "4.0.2",
- "rxjs": "7.8.1",
- "source-map": "0.7.4"
- },
- "engines": {
- "node": "^18.19.1 || ^20.11.1 || >=22.0.0",
- "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
- "yarn": ">= 1.13.0"
- },
- "peerDependencies": {
- "chokidar": "^4.0.0"
- },
- "peerDependenciesMeta": {
- "chokidar": {
- "optional": true
- }
- }
- },
- "node_modules/@angular/cli/node_modules/ajv-formats": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
- "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependenciesMeta": {
- "ajv": {
- "optional": true
- }
- }
- },
- "node_modules/@angular/cli/node_modules/chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "readdirp": "^4.0.1"
- },
- "engines": {
- "node": ">= 14.16.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/@angular/cli/node_modules/picomatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
- "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/@angular/cli/node_modules/readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">= 14.18.0"
- },
- "funding": {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
- }
- },
"node_modules/@angular/cli/node_modules/semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
@@ -1390,9 +981,9 @@
}
},
"node_modules/@angular/common": {
- "version": "19.2.10",
- "resolved": "https://registry.npmjs.org/@angular/common/-/common-19.2.10.tgz",
- "integrity": "sha512-1Aq0pT0MXEYHUXFB0nFkiErW7OUCLxF2XkZv///hxWEBX3nc4Zl+p9yrVRvnTJoLuOVU5TYOPjiyKzAGbUIxVw==",
+ "version": "19.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/common/-/common-19.2.12.tgz",
+ "integrity": "sha512-oE2KLvU+YUyq2pUPS8nBWxkPGj29JzslaFcS9vQgvuKZgBTIwAgKm8QHBsitiZ0V+kX8Agwnl5YSdxdtuV2gQQ==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -1401,14 +992,14 @@
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
- "@angular/core": "19.2.10",
+ "@angular/core": "19.2.12",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/compiler": {
- "version": "19.2.10",
- "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-19.2.10.tgz",
- "integrity": "sha512-XI4VVaTHIsvDu25b/hOqFBIub4RoEVqVrBYo1rKRF9NI+mxg2Wy30qyJ7rYGbF7qUPomC54pen0qQgw359YhMA==",
+ "version": "19.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-19.2.12.tgz",
+ "integrity": "sha512-OXfnkrtPQ1n64zWv3I0Zhk4U+Xtsbc7Utc15vqc+9sYewFjH0Fdb7bsNZ31+unpn+icWlYELtGINh+ld/rqKQQ==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -1418,9 +1009,9 @@
}
},
"node_modules/@angular/compiler-cli": {
- "version": "19.2.10",
- "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-19.2.10.tgz",
- "integrity": "sha512-g+Zg87IbQSmoIVw4fnqBcz3csTaSWDNTabt4JAlNdcoIw88+WsyZVjxPBFFvezsDEd6eFvNdqVJdAUyLhieDYA==",
+ "version": "19.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-19.2.12.tgz",
+ "integrity": "sha512-w8XMBF9ifuQoL5+1dLTdiTGKHV5mwXVrQyfQufmlBUYkmMU0qVcx7OoabHkwols0LGhYY/vcoxWgM5eyUSF41A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1442,44 +1033,14 @@
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
- "@angular/compiler": "19.2.10",
+ "@angular/compiler": "19.2.12",
"typescript": ">=5.5 <5.9"
}
},
- "node_modules/@angular/compiler-cli/node_modules/chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "readdirp": "^4.0.1"
- },
- "engines": {
- "node": ">= 14.16.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/@angular/compiler-cli/node_modules/readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 14.18.0"
- },
- "funding": {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
- }
- },
"node_modules/@angular/core": {
- "version": "19.2.10",
- "resolved": "https://registry.npmjs.org/@angular/core/-/core-19.2.10.tgz",
- "integrity": "sha512-9SQj5zz9VL4nD2dnqaS8nHxYWWQTPavacwG/e5I1wlctrUIGAvvl9uApxXanqlNVoezA0isUVzZGjaiQuu0hBQ==",
+ "version": "19.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/core/-/core-19.2.12.tgz",
+ "integrity": "sha512-NO/EfB1er3627Y9vxEw+VjR59g3fKigAX03DMHF4nyIsjxFZVtzKYztx7hfpCQ4f4kgqKz6WKi3ueUyyyziFqQ==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -1493,9 +1054,9 @@
}
},
"node_modules/@angular/forms": {
- "version": "19.2.10",
- "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-19.2.10.tgz",
- "integrity": "sha512-IUO+kvW1hIMuWAf98YNRLu+L0Kwq4Bd66ga5veJZ0aqnwA95m3HtcGhB9kygKyF/evQYO+X8GC/Hn1knPjkk1w==",
+ "version": "19.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-19.2.12.tgz",
+ "integrity": "sha512-hHJ+YoofahQvGlKvKxgj2vdGwP/D6RYoDtD/MLj41ZIIok4dmk9Rlty1pa3/FfluFK+tHX+CEYqfB+oShtPeaQ==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -1504,16 +1065,33 @@
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
- "@angular/common": "19.2.10",
- "@angular/core": "19.2.10",
- "@angular/platform-browser": "19.2.10",
+ "@angular/common": "19.2.12",
+ "@angular/core": "19.2.12",
+ "@angular/platform-browser": "19.2.12",
+ "rxjs": "^6.5.3 || ^7.4.0"
+ }
+ },
+ "node_modules/@angular/material": {
+ "version": "19.2.17",
+ "resolved": "https://registry.npmjs.org/@angular/material/-/material-19.2.17.tgz",
+ "integrity": "sha512-IyA+KP+uUj3r9loqGJrj7qAiEBckj7EVIdV0jlYwqWIUyKWeJ3R88GmLPMH2BgtBU3R/WkS2blXDI0yvRhKfww==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.3.0"
+ },
+ "peerDependencies": {
+ "@angular/cdk": "19.2.17",
+ "@angular/common": "^19.0.0 || ^20.0.0",
+ "@angular/core": "^19.0.0 || ^20.0.0",
+ "@angular/forms": "^19.0.0 || ^20.0.0",
+ "@angular/platform-browser": "^19.0.0 || ^20.0.0",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/platform-browser": {
- "version": "19.2.10",
- "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-19.2.10.tgz",
- "integrity": "sha512-MYX4av0UvCmtUyCo6vW2RjOT2nbZsChhNjKr70DrWcCRMhYYv4cB4PMr6PnyGKJb7QZSSdzbqbbGtvvaB/quqw==",
+ "version": "19.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-19.2.12.tgz",
+ "integrity": "sha512-R1LF7zvO2CxcJGn8tvpZQPH773iLDMVEGMZc2pRsQoehqgRmos2qjdML4DgVFF1Ismu9TxEtBfIzOiHCSOg2Zg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -1522,9 +1100,9 @@
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
- "@angular/animations": "19.2.10",
- "@angular/common": "19.2.10",
- "@angular/core": "19.2.10"
+ "@angular/animations": "19.2.12",
+ "@angular/common": "19.2.12",
+ "@angular/core": "19.2.12"
},
"peerDependenciesMeta": {
"@angular/animations": {
@@ -1533,9 +1111,9 @@
}
},
"node_modules/@angular/platform-browser-dynamic": {
- "version": "19.2.10",
- "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-19.2.10.tgz",
- "integrity": "sha512-ETyTBTL/Kt17BjTDGXoUJVfjx1ZI0ryg3gvEvvtiMfBcmT/ohiIstYfaoF1QSPVMtbiTRLbCtdE439e4B7YoaQ==",
+ "version": "19.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-19.2.12.tgz",
+ "integrity": "sha512-nU7ASPG6RKtu8sBIY76VEFJ210ZCZ7lD9ANNKDe0Cjo1fI2WrZ9YpcOi4RlGZ4Zq/paF1yg37fIw+hPLZGzZMQ==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -1544,16 +1122,16 @@
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
- "@angular/common": "19.2.10",
- "@angular/compiler": "19.2.10",
- "@angular/core": "19.2.10",
- "@angular/platform-browser": "19.2.10"
+ "@angular/common": "19.2.12",
+ "@angular/compiler": "19.2.12",
+ "@angular/core": "19.2.12",
+ "@angular/platform-browser": "19.2.12"
}
},
"node_modules/@angular/router": {
- "version": "19.2.10",
- "resolved": "https://registry.npmjs.org/@angular/router/-/router-19.2.10.tgz",
- "integrity": "sha512-bmt3ws3L4pYTlI8FW5L1ShyOeUQobXA7wy6TbcIToEonZk+i8uj3xlTD9Ymnjkb9wbNXrD1qoNSpP86c7C1rWQ==",
+ "version": "19.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/router/-/router-19.2.12.tgz",
+ "integrity": "sha512-T2VM8QjKukLnPVyWe4EavSIsBmt91wGVTP7AK8rpWNdMXhVjn8m4iPJGT2Ne0Bqh4kTmISOPZPDEA6A6Q78NvQ==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -1562,9 +1140,9 @@
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
- "@angular/common": "19.2.10",
- "@angular/core": "19.2.10",
- "@angular/platform-browser": "19.2.10",
+ "@angular/common": "19.2.12",
+ "@angular/core": "19.2.12",
+ "@angular/platform-browser": "19.2.12",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
@@ -3718,16 +3296,55 @@
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
+ "node_modules/@eslint/config-array": {
+ "version": "0.20.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz",
+ "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.6",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz",
+ "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.14.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz",
+ "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
"node_modules/@eslint/eslintrc": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
- "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
+ "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
- "espree": "^9.6.0",
- "globals": "^13.19.0",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
@@ -3735,7 +3352,7 @@
"strip-json-comments": "^3.1.1"
},
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
@@ -3746,6 +3363,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -3757,79 +3375,99 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
- "node_modules/@eslint/eslintrc/node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true
- },
"node_modules/@eslint/eslintrc/node_modules/globals": {
- "version": "13.24.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
- "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
"dev": true,
- "dependencies": {
- "type-fest": "^0.20.2"
- },
+ "license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@eslint/eslintrc/node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
"node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
- "node_modules/@eslint/eslintrc/node_modules/type-fest": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
- "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "node_modules/@eslint/js": {
+ "version": "9.27.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz",
+ "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==",
"dev": true,
+ "license": "MIT",
"engines": {
- "node": ">=10"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://eslint.org/donate"
}
},
- "node_modules/@eslint/js": {
- "version": "8.57.1",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
- "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
+ "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
"dev": true,
+ "license": "Apache-2.0",
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
- "node_modules/@humanwhocodes/config-array": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
- "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
- "deprecated": "Use @eslint/config-array instead",
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz",
+ "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==",
"dev": true,
+ "license": "Apache-2.0",
"dependencies": {
- "@humanwhocodes/object-schema": "^2.0.3",
- "debug": "^4.3.1",
- "minimatch": "^3.0.5"
+ "@eslint/core": "^0.14.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.6",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
+ "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.3.0"
},
"engines": {
- "node": ">=10.10.0"
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
+ "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@humanwhocodes/module-importer": {
@@ -3845,12 +3483,19 @@
"url": "https://github.com/sponsors/nzakas"
}
},
- "node_modules/@humanwhocodes/object-schema": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
- "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
- "deprecated": "Use @eslint/object-schema instead",
- "dev": true
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
},
"node_modules/@inquirer/checkbox": {
"version": "4.1.6",
@@ -4957,10 +4602,110 @@
"node": ">= 10"
}
},
+ "node_modules/@ngrx/effects": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-19.2.0.tgz",
+ "integrity": "sha512-DIoFdEdSehAMHUNTWIdl94HjhSh1ZRx0Rgtgp1TjHHyjLiS+vbMmDgPjrCkBv5lT/pEaKbHKnYxjY3CQiW2Hsg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@angular/core": "^19.0.0",
+ "@ngrx/store": "19.2.0",
+ "rxjs": "^6.5.3 || ^7.5.0"
+ }
+ },
+ "node_modules/@ngrx/entity": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@ngrx/entity/-/entity-19.2.0.tgz",
+ "integrity": "sha512-JxKFBk0LAHrmCGLQFQeT8mZhwTZPKzq0m0gqCtXgmtzHj9B/ln3yluTtBWgOEf07dDAkEX/q42Sr+kO+5kctvg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@angular/core": "^19.0.0",
+ "@ngrx/store": "19.2.0",
+ "rxjs": "^6.5.3 || ^7.5.0"
+ }
+ },
+ "node_modules/@ngrx/eslint-plugin": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@ngrx/eslint-plugin/-/eslint-plugin-19.2.0.tgz",
+ "integrity": "sha512-dOHjJCAsv8aI53EwFKh33wCgrx8W58Y3cvlt/+pu8WCIjVkRwZKXZH05ufEPdqp6+xKp0NZuP7fe/F82vaNF4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.3.5",
+ "strip-json-comments": "3.1.1"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/utils": "^8.0.0",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": "*",
+ "typescript-eslint": "^8.0.0"
+ }
+ },
+ "node_modules/@ngrx/operators": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@ngrx/operators/-/operators-19.2.0.tgz",
+ "integrity": "sha512-kv3hFlpWbZxfyILvQAJT2JNbsRGauUIj67U6zOUd8psD7qoJdtdUAZmr/LUgu/6/tweYDUj1mcQJfvaudik0ZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.3.0"
+ },
+ "peerDependencies": {
+ "rxjs": "^6.5.3 || ^7.4.0"
+ }
+ },
+ "node_modules/@ngrx/router-store": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@ngrx/router-store/-/router-store-19.2.0.tgz",
+ "integrity": "sha512-emR6Y+NIcFxFt1QsyDdMIVhkuGEzawGZM5yOo8A6kUZljzf88S/7tHXQRKLz1Vy2fpDRZDO6r/0eagW0JDMfLA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@angular/common": "^19.0.0",
+ "@angular/core": "^19.0.0",
+ "@angular/router": "^19.0.0",
+ "@ngrx/store": "19.2.0",
+ "rxjs": "^6.5.3 || ^7.5.0"
+ }
+ },
+ "node_modules/@ngrx/store": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-19.2.0.tgz",
+ "integrity": "sha512-k2n/jLJZ75Z5rd5vPa2mXPYG/On2rFLiNdrccs9Dw2r+oJosORMlN5TbdsGHhVDFfjzbY9a7JbHUE3YOa69gqw==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@angular/core": "^19.0.0",
+ "rxjs": "^6.5.3 || ^7.5.0"
+ }
+ },
+ "node_modules/@ngrx/store-devtools": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-19.2.0.tgz",
+ "integrity": "sha512-AKlXHsuSRJgYYxmrXZ8WWnDxqgKMG0+HP+IIDmk5h5Z5RIkOLHk6ZGKbakhIiFlL8d16N+GcJ76rqUajaHT+0w==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@angular/core": "^19.0.0",
+ "@ngrx/store": "19.2.0",
+ "rxjs": "^6.5.3 || ^7.5.0"
+ }
+ },
"node_modules/@ngtools/webpack": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-19.2.12.tgz",
- "integrity": "sha512-MTxkM+jZPQP55q0BWx/1w2kaN9mSFC14V9+p4sfNm/OXk7fibtxz5lXH/2sDGFWJi36s4gppKqfHBhp9OTdHCQ==",
+ "version": "19.2.13",
+ "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-19.2.13.tgz",
+ "integrity": "sha512-9dYfLsqWFTn1YVUiWydSp2bboaSW+byeZRFx8qeR7lsOkDGbm/idG68IXFHybHtZ3ptJ5fEeuw89RL47SQ61oA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -5821,9 +5566,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.2.tgz",
- "integrity": "sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz",
+ "integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==",
"cpu": [
"riscv64"
],
@@ -5920,14 +5665,14 @@
]
},
"node_modules/@schematics/angular": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-19.2.12.tgz",
- "integrity": "sha512-6S6tclFctLrjMvhpi8eVvswIpXqlybRpZLCTWyVeWIC6PHYLEyFmFoOhuhcSmOdtnwudvzOt6xWnWEVb3qXZbQ==",
+ "version": "19.2.13",
+ "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-19.2.13.tgz",
+ "integrity": "sha512-SOpK4AwH0isXo7Y2SkgXLyGLMw4GxWPAun6sCLiprmop4KlqKGGALn4xIW0yjq0s5GS0Vx0FFjz8bBfPkgnawA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@angular-devkit/core": "19.2.12",
- "@angular-devkit/schematics": "19.2.12",
+ "@angular-devkit/core": "19.2.13",
+ "@angular-devkit/schematics": "19.2.13",
"jsonc-parser": "3.3.1"
},
"engines": {
@@ -5936,99 +5681,6 @@
"yarn": ">= 1.13.0"
}
},
- "node_modules/@schematics/angular/node_modules/@angular-devkit/core": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.12.tgz",
- "integrity": "sha512-v5pdfZHZ8MTZozfpkhKoPFBpXQW+2GFbTfdyis8FBtevJWCbIsCR3xhodgI4jwzkSEAraN4oVtWvSytdNyBC6A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "8.17.1",
- "ajv-formats": "3.0.1",
- "jsonc-parser": "3.3.1",
- "picomatch": "4.0.2",
- "rxjs": "7.8.1",
- "source-map": "0.7.4"
- },
- "engines": {
- "node": "^18.19.1 || ^20.11.1 || >=22.0.0",
- "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
- "yarn": ">= 1.13.0"
- },
- "peerDependencies": {
- "chokidar": "^4.0.0"
- },
- "peerDependenciesMeta": {
- "chokidar": {
- "optional": true
- }
- }
- },
- "node_modules/@schematics/angular/node_modules/ajv-formats": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
- "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependenciesMeta": {
- "ajv": {
- "optional": true
- }
- }
- },
- "node_modules/@schematics/angular/node_modules/chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "readdirp": "^4.0.1"
- },
- "engines": {
- "node": ">= 14.16.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/@schematics/angular/node_modules/picomatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
- "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/@schematics/angular/node_modules/readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">= 14.18.0"
- },
- "funding": {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
- }
- },
"node_modules/@sigstore/bundle": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.1.0.tgz",
@@ -6265,9 +5917,9 @@
"license": "MIT"
},
"node_modules/@types/express": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
- "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
+ "version": "4.17.22",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.22.tgz",
+ "integrity": "sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6321,10 +5973,11 @@
}
},
"node_modules/@types/jasmine": {
- "version": "4.3.6",
- "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.3.6.tgz",
- "integrity": "sha512-3N0FpQTeiWjm+Oo1WUYWguUS7E6JLceiGTriFrG8k5PU7zRLJCzLcWURU3wjMbZGS//a2/LgjsnO3QxIlwxt9g==",
- "dev": true
+ "version": "5.1.8",
+ "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.8.tgz",
+ "integrity": "sha512-u7/CnvRdh6AaaIzYjCgUuVbREFgulhX05Qtf6ZtW+aOcjCKKVvKgpkPYJBFTZSHtFBYimzU4zP0V2vrEsq9Wcg==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
@@ -6359,9 +6012,9 @@
}
},
"node_modules/@types/qs": {
- "version": "6.9.18",
- "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz",
- "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==",
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
"dev": true,
"license": "MIT"
},
@@ -6661,12 +6314,6 @@
"url": "https://opencollective.com/eslint"
}
},
- "node_modules/@ungap/structured-clone": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
- "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
- "dev": true
- },
"node_modules/@vitejs/plugin-basic-ssl": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.2.0.tgz",
@@ -6910,6 +6557,7 @@
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"dev": true,
+ "license": "MIT",
"peerDependencies": {
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
@@ -7064,6 +6712,7 @@
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
+ "license": "ISC",
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@@ -7072,6 +6721,13 @@
"node": ">= 8"
}
},
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
"node_modules/aria-query": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
@@ -7283,6 +6939,7 @@
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=8"
},
@@ -7687,28 +7344,19 @@
"license": "MIT"
},
"node_modules/chokidar": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
- "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "anymatch": "~3.1.2",
- "braces": "~3.0.2",
- "glob-parent": "~5.1.2",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.6.0"
+ "readdirp": "^4.0.1"
},
"engines": {
- "node": ">= 8.10.0"
+ "node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2"
}
},
"node_modules/chownr": {
@@ -8185,26 +7833,6 @@
}
}
},
- "node_modules/cosmiconfig/node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true,
- "license": "Python-2.0"
- },
- "node_modules/cosmiconfig/node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -8480,18 +8108,6 @@
"node": ">=6"
}
},
- "node_modules/doctrine": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
- "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
- "dev": true,
- "dependencies": {
- "esutils": "^2.0.2"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
"node_modules/dom-serialize": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz",
@@ -8872,59 +8488,64 @@
"dev": true
},
"node_modules/eslint": {
- "version": "8.57.1",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
- "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
- "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+ "version": "9.27.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz",
+ "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.6.1",
- "@eslint/eslintrc": "^2.1.4",
- "@eslint/js": "8.57.1",
- "@humanwhocodes/config-array": "^0.13.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.20.0",
+ "@eslint/config-helpers": "^0.2.1",
+ "@eslint/core": "^0.14.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.27.0",
+ "@eslint/plugin-kit": "^0.3.1",
+ "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
- "@nodelib/fs.walk": "^1.2.8",
- "@ungap/structured-clone": "^1.2.0",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "@types/json-schema": "^7.0.15",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
- "cross-spawn": "^7.0.2",
+ "cross-spawn": "^7.0.6",
"debug": "^4.3.2",
- "doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^7.2.2",
- "eslint-visitor-keys": "^3.4.3",
- "espree": "^9.6.1",
- "esquery": "^1.4.2",
+ "eslint-scope": "^8.3.0",
+ "eslint-visitor-keys": "^4.2.0",
+ "espree": "^10.3.0",
+ "esquery": "^1.5.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^6.0.1",
+ "file-entry-cache": "^8.0.0",
"find-up": "^5.0.0",
"glob-parent": "^6.0.2",
- "globals": "^13.19.0",
- "graphemer": "^1.4.0",
"ignore": "^5.2.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
- "is-path-inside": "^3.0.3",
- "js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
- "levn": "^0.4.1",
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
- "optionator": "^0.9.3",
- "strip-ansi": "^6.0.1",
- "text-table": "^0.2.0"
+ "optionator": "^0.9.3"
},
"bin": {
"eslint": "bin/eslint.js"
},
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
- "url": "https://opencollective.com/eslint"
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
}
},
"node_modules/eslint-scope": {
@@ -8977,35 +8598,43 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
- "node_modules/eslint/node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true
- },
"node_modules/eslint/node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
"engines": {
- "node": ">=10"
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint/node_modules/eslint-scope": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
+ "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/eslint/node_modules/eslint-scope": {
- "version": "7.2.2",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
- "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "node_modules/eslint/node_modules/eslint-visitor-keys": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
"dev": true,
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- },
+ "license": "Apache-2.0",
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
@@ -9039,33 +8668,6 @@
"node": ">=10.13.0"
}
},
- "node_modules/eslint/node_modules/globals": {
- "version": "13.24.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
- "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
- "dev": true,
- "dependencies": {
- "type-fest": "^0.20.2"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eslint/node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
"node_modules/eslint/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -9117,18 +8719,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/eslint/node_modules/type-fest": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
- "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
- "dev": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/eslint/node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -9142,17 +8732,31 @@
}
},
"node_modules/espree": {
- "version": "9.6.1",
- "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
- "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
+ "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
"dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
- "acorn": "^8.9.0",
+ "acorn": "^8.14.0",
"acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^3.4.1"
+ "eslint-visitor-keys": "^4.2.0"
},
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree/node_modules/eslint-visitor-keys": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
@@ -9441,15 +9045,16 @@
}
},
"node_modules/file-entry-cache": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
- "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "flat-cache": "^3.0.4"
+ "flat-cache": "^4.0.0"
},
"engines": {
- "node": "^10.12.0 || >=12.0.0"
+ "node": ">=16.0.0"
}
},
"node_modules/fill-range": {
@@ -9564,17 +9169,17 @@
}
},
"node_modules/flat-cache": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
- "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"flatted": "^3.2.9",
- "keyv": "^4.5.3",
- "rimraf": "^3.0.2"
+ "keyv": "^4.5.4"
},
"engines": {
- "node": "^10.12.0 || >=12.0.0"
+ "node": ">=16"
}
},
"node_modules/flatted": {
@@ -10224,6 +9829,7 @@
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">= 4"
}
@@ -10385,6 +9991,7 @@
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"binary-extensions": "^2.0.0"
},
@@ -10504,15 +10111,6 @@
"node": ">=0.12.0"
}
},
- "node_modules/is-path-inside": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
- "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/is-plain-obj": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz",
@@ -10707,10 +10305,11 @@
}
},
"node_modules/jasmine-core": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.1.tgz",
- "integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==",
- "dev": true
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.7.1.tgz",
+ "integrity": "sha512-QnurrtpKsPoixxG2R3d1xP0St/2kcX5oTZyDyQJMY+Vzi/HUlu1kGm+2V8Tz+9lV991leB1l0xcsyz40s9xOOw==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/jest-worker": {
"version": "27.5.1",
@@ -10759,6 +10358,19 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
},
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
"node_modules/jsbn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
@@ -10783,7 +10395,8 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
@@ -10931,6 +10544,13 @@
"karma-jasmine": "^5.0.0"
}
},
+ "node_modules/karma-jasmine/node_modules/jasmine-core": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.1.tgz",
+ "integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/karma-source-map-support": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz",
@@ -10940,6 +10560,31 @@
"source-map-support": "^0.5.5"
}
},
+ "node_modules/karma/node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
"node_modules/karma/node_modules/cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -10951,6 +10596,19 @@
"wrap-ansi": "^7.0.0"
}
},
+ "node_modules/karma/node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
"node_modules/karma/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -11001,6 +10659,7 @@
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"json-buffer": "3.0.1"
}
@@ -11674,9 +11333,9 @@
}
},
"node_modules/memfs": {
- "version": "4.17.1",
- "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.1.tgz",
- "integrity": "sha512-thuTRd7F4m4dReCIy7vv4eNYnU6XI/tHMLSMMHLiortw/Y0QxqKtinG523U2aerzwYWGi606oBP4oMPy4+edag==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz",
+ "integrity": "sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -12028,9 +11687,9 @@
"dev": true
},
"node_modules/msgpackr": {
- "version": "1.11.2",
- "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.2.tgz",
- "integrity": "sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==",
+ "version": "1.11.4",
+ "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.4.tgz",
+ "integrity": "sha512-uaff7RG9VIC4jacFW9xzL3jc0iM32DNHe4jYVycBcjUePT/Klnfj7pqtWJt9khvDFizmjN2TlYniYmSS2LIaZg==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -12761,7 +12420,6 @@
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"entities": "^6.0.0"
@@ -12802,7 +12460,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
"integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
- "dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
@@ -13102,10 +12759,11 @@
}
},
"node_modules/prettier": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
- "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
+ "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
+ "license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -13277,15 +12935,17 @@
}
},
"node_modules/readdirp": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
- "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
- "dependencies": {
- "picomatch": "^2.2.1"
- },
+ "license": "MIT",
"engines": {
- "node": ">=8.10.0"
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
}
},
"node_modules/reflect-metadata": {
@@ -13601,9 +13261,10 @@
}
},
"node_modules/rxjs": {
- "version": "7.8.1",
- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
- "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
+ "version": "7.8.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
+ "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+ "license": "Apache-2.0",
"dependencies": {
"tslib": "^2.1.0"
}
@@ -13696,36 +13357,6 @@
}
}
},
- "node_modules/sass/node_modules/chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "readdirp": "^4.0.1"
- },
- "engines": {
- "node": ">= 14.16.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/sass/node_modules/readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 14.18.0"
- },
- "funding": {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
- }
- },
"node_modules/sax": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
@@ -14705,12 +14336,6 @@
}
}
},
- "node_modules/text-table": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
- "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
- "dev": true
- },
"node_modules/thingies": {
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz",
@@ -14811,9 +14436,9 @@
}
},
"node_modules/tree-dump": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz",
- "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.3.tgz",
+ "integrity": "sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -14927,6 +14552,30 @@
"node": ">=14.17"
}
},
+ "node_modules/typescript-eslint": {
+ "version": "8.32.1",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.32.1.tgz",
+ "integrity": "sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.32.1",
+ "@typescript-eslint/parser": "8.32.1",
+ "@typescript-eslint/utils": "8.32.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
"node_modules/ua-parser-js": {
"version": "0.7.39",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.39.tgz",
@@ -15241,9 +14890,9 @@
}
},
"node_modules/vite/node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.2.tgz",
- "integrity": "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz",
+ "integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==",
"cpu": [
"arm"
],
@@ -15256,9 +14905,9 @@
"peer": true
},
"node_modules/vite/node_modules/@rollup/rollup-android-arm64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.2.tgz",
- "integrity": "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz",
+ "integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==",
"cpu": [
"arm64"
],
@@ -15271,9 +14920,9 @@
"peer": true
},
"node_modules/vite/node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.2.tgz",
- "integrity": "sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz",
+ "integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==",
"cpu": [
"arm64"
],
@@ -15286,9 +14935,9 @@
"peer": true
},
"node_modules/vite/node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.2.tgz",
- "integrity": "sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz",
+ "integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==",
"cpu": [
"x64"
],
@@ -15301,9 +14950,9 @@
"peer": true
},
"node_modules/vite/node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.2.tgz",
- "integrity": "sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz",
+ "integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==",
"cpu": [
"arm64"
],
@@ -15316,9 +14965,9 @@
"peer": true
},
"node_modules/vite/node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.2.tgz",
- "integrity": "sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz",
+ "integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==",
"cpu": [
"x64"
],
@@ -15331,9 +14980,9 @@
"peer": true
},
"node_modules/vite/node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.2.tgz",
- "integrity": "sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz",
+ "integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==",
"cpu": [
"arm"
],
@@ -15346,9 +14995,9 @@
"peer": true
},
"node_modules/vite/node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.2.tgz",
- "integrity": "sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz",
+ "integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==",
"cpu": [
"arm"
],
@@ -15361,9 +15010,9 @@
"peer": true
},
"node_modules/vite/node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.2.tgz",
- "integrity": "sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz",
+ "integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==",
"cpu": [
"arm64"
],
@@ -15376,9 +15025,9 @@
"peer": true
},
"node_modules/vite/node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.2.tgz",
- "integrity": "sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz",
+ "integrity": "sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==",
"cpu": [
"arm64"
],
@@ -15391,9 +15040,9 @@
"peer": true
},
"node_modules/vite/node_modules/@rollup/rollup-linux-loongarch64-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.2.tgz",
- "integrity": "sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz",
+ "integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==",
"cpu": [
"loong64"
],
@@ -15406,9 +15055,9 @@
"peer": true
},
"node_modules/vite/node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.2.tgz",
- "integrity": "sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz",
+ "integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==",
"cpu": [
"ppc64"
],
@@ -15421,9 +15070,9 @@
"peer": true
},
"node_modules/vite/node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.2.tgz",
- "integrity": "sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz",
+ "integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==",
"cpu": [
"riscv64"
],
@@ -15436,9 +15085,9 @@
"peer": true
},
"node_modules/vite/node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.2.tgz",
- "integrity": "sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz",
+ "integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==",
"cpu": [
"s390x"
],
@@ -15451,9 +15100,9 @@
"peer": true
},
"node_modules/vite/node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.2.tgz",
- "integrity": "sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz",
+ "integrity": "sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==",
"cpu": [
"x64"
],
@@ -15466,9 +15115,9 @@
"peer": true
},
"node_modules/vite/node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.2.tgz",
- "integrity": "sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.0.tgz",
+ "integrity": "sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==",
"cpu": [
"x64"
],
@@ -15481,9 +15130,9 @@
"peer": true
},
"node_modules/vite/node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.2.tgz",
- "integrity": "sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz",
+ "integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==",
"cpu": [
"arm64"
],
@@ -15496,9 +15145,9 @@
"peer": true
},
"node_modules/vite/node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.2.tgz",
- "integrity": "sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz",
+ "integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==",
"cpu": [
"ia32"
],
@@ -15511,9 +15160,9 @@
"peer": true
},
"node_modules/vite/node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.2.tgz",
- "integrity": "sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz",
+ "integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==",
"cpu": [
"x64"
],
@@ -15556,9 +15205,9 @@
}
},
"node_modules/vite/node_modules/rollup": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.2.tgz",
- "integrity": "sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.0.tgz",
+ "integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -15573,26 +15222,26 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.40.2",
- "@rollup/rollup-android-arm64": "4.40.2",
- "@rollup/rollup-darwin-arm64": "4.40.2",
- "@rollup/rollup-darwin-x64": "4.40.2",
- "@rollup/rollup-freebsd-arm64": "4.40.2",
- "@rollup/rollup-freebsd-x64": "4.40.2",
- "@rollup/rollup-linux-arm-gnueabihf": "4.40.2",
- "@rollup/rollup-linux-arm-musleabihf": "4.40.2",
- "@rollup/rollup-linux-arm64-gnu": "4.40.2",
- "@rollup/rollup-linux-arm64-musl": "4.40.2",
- "@rollup/rollup-linux-loongarch64-gnu": "4.40.2",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.40.2",
- "@rollup/rollup-linux-riscv64-gnu": "4.40.2",
- "@rollup/rollup-linux-riscv64-musl": "4.40.2",
- "@rollup/rollup-linux-s390x-gnu": "4.40.2",
- "@rollup/rollup-linux-x64-gnu": "4.40.2",
- "@rollup/rollup-linux-x64-musl": "4.40.2",
- "@rollup/rollup-win32-arm64-msvc": "4.40.2",
- "@rollup/rollup-win32-ia32-msvc": "4.40.2",
- "@rollup/rollup-win32-x64-msvc": "4.40.2",
+ "@rollup/rollup-android-arm-eabi": "4.41.0",
+ "@rollup/rollup-android-arm64": "4.41.0",
+ "@rollup/rollup-darwin-arm64": "4.41.0",
+ "@rollup/rollup-darwin-x64": "4.41.0",
+ "@rollup/rollup-freebsd-arm64": "4.41.0",
+ "@rollup/rollup-freebsd-x64": "4.41.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.41.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.41.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.41.0",
+ "@rollup/rollup-linux-arm64-musl": "4.41.0",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.41.0",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.41.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.41.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.41.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.41.0",
+ "@rollup/rollup-linux-x64-gnu": "4.41.0",
+ "@rollup/rollup-linux-x64-musl": "4.41.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.41.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.41.0",
+ "@rollup/rollup-win32-x64-msvc": "4.41.0",
"fsevents": "~2.3.2"
}
},
@@ -15779,6 +15428,31 @@
}
}
},
+ "node_modules/webpack-dev-server/node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
"node_modules/webpack-dev-server/node_modules/http-proxy-middleware": {
"version": "2.0.9",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz",
@@ -15798,10 +15472,23 @@
"peerDependencies": {
"@types/express": "^4.17.13"
},
- "peerDependenciesMeta": {
- "@types/express": {
- "optional": true
- }
+ "peerDependenciesMeta": {
+ "@types/express": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/webpack-dev-server/node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
}
},
"node_modules/webpack-merge": {
@@ -16053,76 +15740,37 @@
}
},
"@angular-devkit/architect": {
- "version": "0.1902.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.12.tgz",
- "integrity": "sha512-LfUc7k84YL290hAxsG+FvjQpXugQXyw5aDzrQQB4iTYhBgaABu2aaNOU4eu3JH+F8NeXd2EBF/YMr2LDSkYlMw==",
+ "version": "0.1902.13",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.13.tgz",
+ "integrity": "sha512-ZMj+PjK22Ph2U8usG6L7LqEfvWlbaOvmiWXSrEt9YiC9QJt6rsumCkOgUIsmHQtucm/lK+9CMtyYdwH2fYycjg==",
"dev": true,
"requires": {
- "@angular-devkit/core": "19.2.12",
+ "@angular-devkit/core": "19.2.13",
"rxjs": "7.8.1"
},
"dependencies": {
- "@angular-devkit/core": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.12.tgz",
- "integrity": "sha512-v5pdfZHZ8MTZozfpkhKoPFBpXQW+2GFbTfdyis8FBtevJWCbIsCR3xhodgI4jwzkSEAraN4oVtWvSytdNyBC6A==",
- "dev": true,
- "requires": {
- "ajv": "8.17.1",
- "ajv-formats": "3.0.1",
- "jsonc-parser": "3.3.1",
- "picomatch": "4.0.2",
- "rxjs": "7.8.1",
- "source-map": "0.7.4"
- }
- },
- "ajv-formats": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
- "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
- "dev": true,
- "requires": {
- "ajv": "^8.0.0"
- }
- },
- "chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"dev": true,
- "optional": true,
- "peer": true,
"requires": {
- "readdirp": "^4.0.1"
+ "tslib": "^2.1.0"
}
- },
- "picomatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
- "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
- "dev": true
- },
- "readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
- "optional": true,
- "peer": true
}
}
},
"@angular-devkit/build-angular": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-19.2.12.tgz",
- "integrity": "sha512-gPx3Vi7QFzHkSV388en6VqSqasojitJKuKmgTMPOV5keLtpOylPv3rjnr8oO9rYbYmLsT/WTUsP7bYiZhrr19Q==",
+ "version": "19.2.13",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-19.2.13.tgz",
+ "integrity": "sha512-MrNpwrCq6COszhxyD/u2LE0yygTEjIAlaKaIvvDi9nurzUoKRc1vIJWeB2VkGgmUEjj6OTEeM/6zbo02s88EzA==",
"dev": true,
"requires": {
"@ampproject/remapping": "2.3.0",
- "@angular-devkit/architect": "0.1902.12",
- "@angular-devkit/build-webpack": "0.1902.12",
- "@angular-devkit/core": "19.2.12",
- "@angular/build": "19.2.12",
+ "@angular-devkit/architect": "0.1902.13",
+ "@angular-devkit/build-webpack": "0.1902.13",
+ "@angular-devkit/core": "19.2.13",
+ "@angular/build": "19.2.13",
"@babel/core": "7.26.10",
"@babel/generator": "7.26.10",
"@babel/helper-annotate-as-pure": "7.25.9",
@@ -16133,7 +15781,7 @@
"@babel/preset-env": "7.26.9",
"@babel/runtime": "7.26.10",
"@discoveryjs/json-ext": "0.6.3",
- "@ngtools/webpack": "19.2.12",
+ "@ngtools/webpack": "19.2.13",
"@vitejs/plugin-basic-ssl": "1.2.0",
"ansi-colors": "4.1.3",
"autoprefixer": "10.4.20",
@@ -16176,20 +15824,6 @@
"webpack-subresource-integrity": "5.1.0"
},
"dependencies": {
- "@angular-devkit/core": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.12.tgz",
- "integrity": "sha512-v5pdfZHZ8MTZozfpkhKoPFBpXQW+2GFbTfdyis8FBtevJWCbIsCR3xhodgI4jwzkSEAraN4oVtWvSytdNyBC6A==",
- "dev": true,
- "requires": {
- "ajv": "8.17.1",
- "ajv-formats": "3.0.1",
- "jsonc-parser": "3.3.1",
- "picomatch": "4.0.2",
- "rxjs": "7.8.1",
- "source-map": "0.7.4"
- }
- },
"@babel/core": {
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz",
@@ -16221,26 +15855,6 @@
}
}
},
- "ajv-formats": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
- "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
- "dev": true,
- "requires": {
- "ajv": "^8.0.0"
- }
- },
- "chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
- "optional": true,
- "peer": true,
- "requires": {
- "readdirp": "^4.0.1"
- }
- },
"convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -16277,13 +15891,14 @@
"source-map-js": "^1.2.1"
}
},
- "readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"dev": true,
- "optional": true,
- "peer": true
+ "requires": {
+ "tslib": "^2.1.0"
+ }
},
"semver": {
"version": "7.7.1",
@@ -16294,42 +15909,40 @@
}
},
"@angular-devkit/build-webpack": {
- "version": "0.1902.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1902.12.tgz",
- "integrity": "sha512-JNwvzaN2RVbG1IClFPXhNpysVwf55nWmVsNN5iQHRXkD3kpqnaOfhUBtlhBBjLf/i6cwKEne2TI8zciaEYr+iw==",
+ "version": "0.1902.13",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1902.13.tgz",
+ "integrity": "sha512-upb+cKWkuXwmKyppSwZf3ryHWPm4aS6sJkQu0TWh4RoMRp1WCYVxUfgZ28fTMqcBF3eoFy2XPjdOfkJDRb6Hrg==",
"dev": true,
"requires": {
- "@angular-devkit/architect": "0.1902.12",
+ "@angular-devkit/architect": "0.1902.13",
"rxjs": "7.8.1"
+ },
+ "dependencies": {
+ "rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
+ "dev": true,
+ "requires": {
+ "tslib": "^2.1.0"
+ }
+ }
}
},
- "@angular-devkit/schematics": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.12.tgz",
- "integrity": "sha512-vK5NI/asi1snWFkw02DpmC8tLq6u5ZbUwwXxgALKuVwGl3g1VLzrHrkoSCrcsOO9Nu6GQOPbxax2lR/DICmytg==",
+ "@angular-devkit/core": {
+ "version": "19.2.13",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.13.tgz",
+ "integrity": "sha512-iq73hE5Uvms1w3uMUSk4i4NDXDMQ863VAifX8LOTadhG6U0xISjNJ11763egVCxQmaKmg7zbG4rda88wHJATzA==",
"dev": true,
"requires": {
- "@angular-devkit/core": "19.2.12",
+ "ajv": "8.17.1",
+ "ajv-formats": "3.0.1",
"jsonc-parser": "3.3.1",
- "magic-string": "0.30.17",
- "ora": "5.4.1",
- "rxjs": "7.8.1"
+ "picomatch": "4.0.2",
+ "rxjs": "7.8.1",
+ "source-map": "0.7.4"
},
"dependencies": {
- "@angular-devkit/core": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.12.tgz",
- "integrity": "sha512-v5pdfZHZ8MTZozfpkhKoPFBpXQW+2GFbTfdyis8FBtevJWCbIsCR3xhodgI4jwzkSEAraN4oVtWvSytdNyBC6A==",
- "dev": true,
- "requires": {
- "ajv": "8.17.1",
- "ajv-formats": "3.0.1",
- "jsonc-parser": "3.3.1",
- "picomatch": "4.0.2",
- "rxjs": "7.8.1",
- "source-map": "0.7.4"
- }
- },
"ajv-formats": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
@@ -16339,93 +15952,57 @@
"ajv": "^8.0.0"
}
},
- "chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
- "optional": true,
- "peer": true,
- "requires": {
- "readdirp": "^4.0.1"
- }
- },
"picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true
},
- "readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"dev": true,
- "optional": true,
- "peer": true
+ "requires": {
+ "tslib": "^2.1.0"
+ }
}
}
},
- "@angular-eslint/builder": {
- "version": "19.4.0",
- "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-19.4.0.tgz",
- "integrity": "sha512-+mI/YwXiT+IPRNuTNrQqOm97iqbPM5Zc4fxR3p9N1xj1dxLyXJrtGXbbWJPK6i74ON7KdpkY3WLhMKnhWM4RzQ==",
+ "@angular-devkit/schematics": {
+ "version": "19.2.13",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.13.tgz",
+ "integrity": "sha512-NhSPz3lI9njEo8eMUlZVGtlXl12UcNZv5lWTBZY/FGWUu6P5ciD/9iJINbc1jiaDH5E/DLEicUNuai0Q91X4Nw==",
"dev": true,
"requires": {
- "@angular-devkit/architect": ">= 0.1900.0 < 0.2000.0",
- "@angular-devkit/core": ">= 19.0.0 < 20.0.0"
+ "@angular-devkit/core": "19.2.13",
+ "jsonc-parser": "3.3.1",
+ "magic-string": "0.30.17",
+ "ora": "5.4.1",
+ "rxjs": "7.8.1"
},
"dependencies": {
- "@angular-devkit/core": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.12.tgz",
- "integrity": "sha512-v5pdfZHZ8MTZozfpkhKoPFBpXQW+2GFbTfdyis8FBtevJWCbIsCR3xhodgI4jwzkSEAraN4oVtWvSytdNyBC6A==",
- "dev": true,
- "requires": {
- "ajv": "8.17.1",
- "ajv-formats": "3.0.1",
- "jsonc-parser": "3.3.1",
- "picomatch": "4.0.2",
- "rxjs": "7.8.1",
- "source-map": "0.7.4"
- }
- },
- "ajv-formats": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
- "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
- "dev": true,
- "requires": {
- "ajv": "^8.0.0"
- }
- },
- "chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"dev": true,
- "optional": true,
- "peer": true,
"requires": {
- "readdirp": "^4.0.1"
+ "tslib": "^2.1.0"
}
- },
- "picomatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
- "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
- "dev": true
- },
- "readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
- "optional": true,
- "peer": true
}
}
},
+ "@angular-eslint/builder": {
+ "version": "19.4.0",
+ "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-19.4.0.tgz",
+ "integrity": "sha512-+mI/YwXiT+IPRNuTNrQqOm97iqbPM5Zc4fxR3p9N1xj1dxLyXJrtGXbbWJPK6i74ON7KdpkY3WLhMKnhWM4RzQ==",
+ "dev": true,
+ "requires": {
+ "@angular-devkit/architect": ">= 0.1900.0 < 0.2000.0",
+ "@angular-devkit/core": ">= 19.0.0 < 20.0.0"
+ }
+ },
"@angular-eslint/bundled-angular-compiler": {
"version": "19.4.0",
"resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-19.4.0.tgz",
@@ -16491,60 +16068,12 @@
"strip-json-comments": "3.1.1"
},
"dependencies": {
- "@angular-devkit/core": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.12.tgz",
- "integrity": "sha512-v5pdfZHZ8MTZozfpkhKoPFBpXQW+2GFbTfdyis8FBtevJWCbIsCR3xhodgI4jwzkSEAraN4oVtWvSytdNyBC6A==",
- "dev": true,
- "requires": {
- "ajv": "8.17.1",
- "ajv-formats": "3.0.1",
- "jsonc-parser": "3.3.1",
- "picomatch": "4.0.2",
- "rxjs": "7.8.1",
- "source-map": "0.7.4"
- }
- },
- "ajv-formats": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
- "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
- "dev": true,
- "requires": {
- "ajv": "^8.0.0"
- }
- },
- "chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
- "optional": true,
- "peer": true,
- "requires": {
- "readdirp": "^4.0.1"
- }
- },
"ignore": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz",
"integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==",
"dev": true
},
- "picomatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
- "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
- "dev": true
- },
- "readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
- "optional": true,
- "peer": true
- },
"semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
@@ -16576,21 +16105,21 @@
}
},
"@angular/animations": {
- "version": "19.2.10",
- "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-19.2.10.tgz",
- "integrity": "sha512-LlH6mt0D8+MI0LV8QNx9/rqLLv0WCfYPS/5iXSGpIyfsflLoO5dRGhtS4pdQbu5gqmnLdf9i7r4ldRJjf7wb+g==",
+ "version": "19.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-19.2.12.tgz",
+ "integrity": "sha512-PEONFwzhUzeAMFW2ehfL+uGnkeVsikoEOJ3RJVdHWH7W8GUgdN2ubogw6umu939/MqL1MsItImXQcBA7aWfzSg==",
"requires": {
"tslib": "^2.3.0"
}
},
"@angular/build": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular/build/-/build-19.2.12.tgz",
- "integrity": "sha512-G28ux1T5QDlWporwupWbcodBN3rcyHfK2Dh5M3UC5hj0GstpfEHcpBHxawZzIxhqPKy//tdVLlzORUgvAwnqbA==",
+ "version": "19.2.13",
+ "resolved": "https://registry.npmjs.org/@angular/build/-/build-19.2.13.tgz",
+ "integrity": "sha512-ABcwhAB9DpsvXY7joRFSKiQCHJmCokVJK1Liuz0/AI9Xlp7spqaWqJcC1DVWO0645tUk4HhYmUh5a68REK1Q1A==",
"dev": true,
"requires": {
"@ampproject/remapping": "2.3.0",
- "@angular-devkit/architect": "0.1902.12",
+ "@angular-devkit/architect": "0.1902.13",
"@babel/core": "7.26.10",
"@babel/helper-annotate-as-pure": "7.25.9",
"@babel/helper-split-export-declaration": "7.24.7",
@@ -16703,18 +16232,27 @@
}
}
},
+ "@angular/cdk": {
+ "version": "19.2.17",
+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.2.17.tgz",
+ "integrity": "sha512-3jG33S+5+kqymCRwQlcSEWlY5rYwkKxe0onln+NXxT0/kteR02vWvv1+Li4/QqSr5JvsGHEhAFsZaR9QtOzbdA==",
+ "requires": {
+ "parse5": "^7.1.2",
+ "tslib": "^2.3.0"
+ }
+ },
"@angular/cli": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.2.12.tgz",
- "integrity": "sha512-cZkHpM16uh3VouHG1XdWSk0ZWisQRxMVADk5IJlM9jMcPqnFyJwD/UXCS+XTaW3POpNDwsmbh2UB9Xabdgo7rw==",
+ "version": "19.2.13",
+ "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.2.13.tgz",
+ "integrity": "sha512-dDRCS73/lrItWx9j4SmwHR56GiZsW8ObNi2q9l/1ny813CG9K43STYFG/wJvGS7ZF3y5hvjIiJOwBx2YIouOIw==",
"dev": true,
"requires": {
- "@angular-devkit/architect": "0.1902.12",
- "@angular-devkit/core": "19.2.12",
- "@angular-devkit/schematics": "19.2.12",
+ "@angular-devkit/architect": "0.1902.13",
+ "@angular-devkit/core": "19.2.13",
+ "@angular-devkit/schematics": "19.2.13",
"@inquirer/prompts": "7.3.2",
"@listr2/prompt-adapter-inquirer": "2.0.18",
- "@schematics/angular": "19.2.12",
+ "@schematics/angular": "19.2.13",
"@yarnpkg/lockfile": "1.1.0",
"ini": "5.0.0",
"jsonc-parser": "3.3.1",
@@ -16728,54 +16266,6 @@
"yargs": "17.7.2"
},
"dependencies": {
- "@angular-devkit/core": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.12.tgz",
- "integrity": "sha512-v5pdfZHZ8MTZozfpkhKoPFBpXQW+2GFbTfdyis8FBtevJWCbIsCR3xhodgI4jwzkSEAraN4oVtWvSytdNyBC6A==",
- "dev": true,
- "requires": {
- "ajv": "8.17.1",
- "ajv-formats": "3.0.1",
- "jsonc-parser": "3.3.1",
- "picomatch": "4.0.2",
- "rxjs": "7.8.1",
- "source-map": "0.7.4"
- }
- },
- "ajv-formats": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
- "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
- "dev": true,
- "requires": {
- "ajv": "^8.0.0"
- }
- },
- "chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
- "optional": true,
- "peer": true,
- "requires": {
- "readdirp": "^4.0.1"
- }
- },
- "picomatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
- "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
- "dev": true
- },
- "readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
- "optional": true,
- "peer": true
- },
"semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
@@ -16785,25 +16275,25 @@
}
},
"@angular/common": {
- "version": "19.2.10",
- "resolved": "https://registry.npmjs.org/@angular/common/-/common-19.2.10.tgz",
- "integrity": "sha512-1Aq0pT0MXEYHUXFB0nFkiErW7OUCLxF2XkZv///hxWEBX3nc4Zl+p9yrVRvnTJoLuOVU5TYOPjiyKzAGbUIxVw==",
+ "version": "19.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/common/-/common-19.2.12.tgz",
+ "integrity": "sha512-oE2KLvU+YUyq2pUPS8nBWxkPGj29JzslaFcS9vQgvuKZgBTIwAgKm8QHBsitiZ0V+kX8Agwnl5YSdxdtuV2gQQ==",
"requires": {
"tslib": "^2.3.0"
}
},
"@angular/compiler": {
- "version": "19.2.10",
- "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-19.2.10.tgz",
- "integrity": "sha512-XI4VVaTHIsvDu25b/hOqFBIub4RoEVqVrBYo1rKRF9NI+mxg2Wy30qyJ7rYGbF7qUPomC54pen0qQgw359YhMA==",
+ "version": "19.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-19.2.12.tgz",
+ "integrity": "sha512-OXfnkrtPQ1n64zWv3I0Zhk4U+Xtsbc7Utc15vqc+9sYewFjH0Fdb7bsNZ31+unpn+icWlYELtGINh+ld/rqKQQ==",
"requires": {
"tslib": "^2.3.0"
}
},
"@angular/compiler-cli": {
- "version": "19.2.10",
- "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-19.2.10.tgz",
- "integrity": "sha512-g+Zg87IbQSmoIVw4fnqBcz3csTaSWDNTabt4JAlNdcoIw88+WsyZVjxPBFFvezsDEd6eFvNdqVJdAUyLhieDYA==",
+ "version": "19.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-19.2.12.tgz",
+ "integrity": "sha512-w8XMBF9ifuQoL5+1dLTdiTGKHV5mwXVrQyfQufmlBUYkmMU0qVcx7OoabHkwols0LGhYY/vcoxWgM5eyUSF41A==",
"dev": true,
"requires": {
"@babel/core": "7.26.9",
@@ -16814,61 +16304,52 @@
"semver": "^7.0.0",
"tslib": "^2.3.0",
"yargs": "^17.2.1"
- },
- "dependencies": {
- "chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
- "requires": {
- "readdirp": "^4.0.1"
- }
- },
- "readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true
- }
}
},
"@angular/core": {
- "version": "19.2.10",
- "resolved": "https://registry.npmjs.org/@angular/core/-/core-19.2.10.tgz",
- "integrity": "sha512-9SQj5zz9VL4nD2dnqaS8nHxYWWQTPavacwG/e5I1wlctrUIGAvvl9uApxXanqlNVoezA0isUVzZGjaiQuu0hBQ==",
+ "version": "19.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/core/-/core-19.2.12.tgz",
+ "integrity": "sha512-NO/EfB1er3627Y9vxEw+VjR59g3fKigAX03DMHF4nyIsjxFZVtzKYztx7hfpCQ4f4kgqKz6WKi3ueUyyyziFqQ==",
"requires": {
"tslib": "^2.3.0"
}
},
"@angular/forms": {
- "version": "19.2.10",
- "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-19.2.10.tgz",
- "integrity": "sha512-IUO+kvW1hIMuWAf98YNRLu+L0Kwq4Bd66ga5veJZ0aqnwA95m3HtcGhB9kygKyF/evQYO+X8GC/Hn1knPjkk1w==",
+ "version": "19.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-19.2.12.tgz",
+ "integrity": "sha512-hHJ+YoofahQvGlKvKxgj2vdGwP/D6RYoDtD/MLj41ZIIok4dmk9Rlty1pa3/FfluFK+tHX+CEYqfB+oShtPeaQ==",
+ "requires": {
+ "tslib": "^2.3.0"
+ }
+ },
+ "@angular/material": {
+ "version": "19.2.17",
+ "resolved": "https://registry.npmjs.org/@angular/material/-/material-19.2.17.tgz",
+ "integrity": "sha512-IyA+KP+uUj3r9loqGJrj7qAiEBckj7EVIdV0jlYwqWIUyKWeJ3R88GmLPMH2BgtBU3R/WkS2blXDI0yvRhKfww==",
"requires": {
"tslib": "^2.3.0"
}
},
"@angular/platform-browser": {
- "version": "19.2.10",
- "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-19.2.10.tgz",
- "integrity": "sha512-MYX4av0UvCmtUyCo6vW2RjOT2nbZsChhNjKr70DrWcCRMhYYv4cB4PMr6PnyGKJb7QZSSdzbqbbGtvvaB/quqw==",
+ "version": "19.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-19.2.12.tgz",
+ "integrity": "sha512-R1LF7zvO2CxcJGn8tvpZQPH773iLDMVEGMZc2pRsQoehqgRmos2qjdML4DgVFF1Ismu9TxEtBfIzOiHCSOg2Zg==",
"requires": {
"tslib": "^2.3.0"
}
},
"@angular/platform-browser-dynamic": {
- "version": "19.2.10",
- "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-19.2.10.tgz",
- "integrity": "sha512-ETyTBTL/Kt17BjTDGXoUJVfjx1ZI0ryg3gvEvvtiMfBcmT/ohiIstYfaoF1QSPVMtbiTRLbCtdE439e4B7YoaQ==",
+ "version": "19.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-19.2.12.tgz",
+ "integrity": "sha512-nU7ASPG6RKtu8sBIY76VEFJ210ZCZ7lD9ANNKDe0Cjo1fI2WrZ9YpcOi4RlGZ4Zq/paF1yg37fIw+hPLZGzZMQ==",
"requires": {
"tslib": "^2.3.0"
}
},
"@angular/router": {
- "version": "19.2.10",
- "resolved": "https://registry.npmjs.org/@angular/router/-/router-19.2.10.tgz",
- "integrity": "sha512-bmt3ws3L4pYTlI8FW5L1ShyOeUQobXA7wy6TbcIToEonZk+i8uj3xlTD9Ymnjkb9wbNXrD1qoNSpP86c7C1rWQ==",
+ "version": "19.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/router/-/router-19.2.12.tgz",
+ "integrity": "sha512-T2VM8QjKukLnPVyWe4EavSIsBmt91wGVTP7AK8rpWNdMXhVjn8m4iPJGT2Ne0Bqh4kTmISOPZPDEA6A6Q78NvQ==",
"requires": {
"tslib": "^2.3.0"
}
@@ -18162,16 +17643,42 @@
"integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
"dev": true
},
+ "@eslint/config-array": {
+ "version": "0.20.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz",
+ "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==",
+ "dev": true,
+ "requires": {
+ "@eslint/object-schema": "^2.1.6",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ }
+ },
+ "@eslint/config-helpers": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz",
+ "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==",
+ "dev": true
+ },
+ "@eslint/core": {
+ "version": "0.14.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz",
+ "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==",
+ "dev": true,
+ "requires": {
+ "@types/json-schema": "^7.0.15"
+ }
+ },
"@eslint/eslintrc": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
- "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
+ "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
"dev": true,
"requires": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
- "espree": "^9.6.0",
- "globals": "^13.19.0",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
@@ -18185,65 +17692,70 @@
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- }
- },
- "argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true
- },
- "globals": {
- "version": "13.24.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
- "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
- "dev": true,
- "requires": {
- "type-fest": "^0.20.2"
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
}
},
- "js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
- "requires": {
- "argparse": "^2.0.1"
- }
+ "globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
- },
- "type-fest": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
- "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
- "dev": true
}
}
},
"@eslint/js": {
- "version": "8.57.1",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
- "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+ "version": "9.27.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz",
+ "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==",
"dev": true
},
- "@humanwhocodes/config-array": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
- "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+ "@eslint/object-schema": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
+ "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
+ "dev": true
+ },
+ "@eslint/plugin-kit": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz",
+ "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==",
"dev": true,
"requires": {
- "@humanwhocodes/object-schema": "^2.0.3",
- "debug": "^4.3.1",
- "minimatch": "^3.0.5"
+ "@eslint/core": "^0.14.0",
+ "levn": "^0.4.1"
+ }
+ },
+ "@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true
+ },
+ "@humanfs/node": {
+ "version": "0.16.6",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
+ "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+ "dev": true,
+ "requires": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.3.0"
+ },
+ "dependencies": {
+ "@humanwhocodes/retry": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
+ "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+ "dev": true
+ }
}
},
"@humanwhocodes/module-importer": {
@@ -18252,10 +17764,10 @@
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
"dev": true
},
- "@humanwhocodes/object-schema": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
- "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
"dev": true
},
"@inquirer/checkbox": {
@@ -18844,10 +18356,68 @@
"dev": true,
"optional": true
},
+ "@ngrx/effects": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-19.2.0.tgz",
+ "integrity": "sha512-DIoFdEdSehAMHUNTWIdl94HjhSh1ZRx0Rgtgp1TjHHyjLiS+vbMmDgPjrCkBv5lT/pEaKbHKnYxjY3CQiW2Hsg==",
+ "requires": {
+ "tslib": "^2.0.0"
+ }
+ },
+ "@ngrx/entity": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@ngrx/entity/-/entity-19.2.0.tgz",
+ "integrity": "sha512-JxKFBk0LAHrmCGLQFQeT8mZhwTZPKzq0m0gqCtXgmtzHj9B/ln3yluTtBWgOEf07dDAkEX/q42Sr+kO+5kctvg==",
+ "requires": {
+ "tslib": "^2.0.0"
+ }
+ },
+ "@ngrx/eslint-plugin": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@ngrx/eslint-plugin/-/eslint-plugin-19.2.0.tgz",
+ "integrity": "sha512-dOHjJCAsv8aI53EwFKh33wCgrx8W58Y3cvlt/+pu8WCIjVkRwZKXZH05ufEPdqp6+xKp0NZuP7fe/F82vaNF4g==",
+ "dev": true,
+ "requires": {
+ "semver": "^7.3.5",
+ "strip-json-comments": "3.1.1"
+ }
+ },
+ "@ngrx/operators": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@ngrx/operators/-/operators-19.2.0.tgz",
+ "integrity": "sha512-kv3hFlpWbZxfyILvQAJT2JNbsRGauUIj67U6zOUd8psD7qoJdtdUAZmr/LUgu/6/tweYDUj1mcQJfvaudik0ZQ==",
+ "requires": {
+ "tslib": "^2.3.0"
+ }
+ },
+ "@ngrx/router-store": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@ngrx/router-store/-/router-store-19.2.0.tgz",
+ "integrity": "sha512-emR6Y+NIcFxFt1QsyDdMIVhkuGEzawGZM5yOo8A6kUZljzf88S/7tHXQRKLz1Vy2fpDRZDO6r/0eagW0JDMfLA==",
+ "requires": {
+ "tslib": "^2.0.0"
+ }
+ },
+ "@ngrx/store": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-19.2.0.tgz",
+ "integrity": "sha512-k2n/jLJZ75Z5rd5vPa2mXPYG/On2rFLiNdrccs9Dw2r+oJosORMlN5TbdsGHhVDFfjzbY9a7JbHUE3YOa69gqw==",
+ "requires": {
+ "tslib": "^2.0.0"
+ }
+ },
+ "@ngrx/store-devtools": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-19.2.0.tgz",
+ "integrity": "sha512-AKlXHsuSRJgYYxmrXZ8WWnDxqgKMG0+HP+IIDmk5h5Z5RIkOLHk6ZGKbakhIiFlL8d16N+GcJ76rqUajaHT+0w==",
+ "requires": {
+ "tslib": "^2.0.0"
+ }
+ },
"@ngtools/webpack": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-19.2.12.tgz",
- "integrity": "sha512-MTxkM+jZPQP55q0BWx/1w2kaN9mSFC14V9+p4sfNm/OXk7fibtxz5lXH/2sDGFWJi36s4gppKqfHBhp9OTdHCQ==",
+ "version": "19.2.13",
+ "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-19.2.13.tgz",
+ "integrity": "sha512-9dYfLsqWFTn1YVUiWydSp2bboaSW+byeZRFx8qeR7lsOkDGbm/idG68IXFHybHtZ3ptJ5fEeuw89RL47SQ61oA==",
"dev": true,
"requires": {}
},
@@ -19312,9 +18882,9 @@
"optional": true
},
"@rollup/rollup-linux-riscv64-musl": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.2.tgz",
- "integrity": "sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz",
+ "integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==",
"dev": true,
"optional": true,
"peer": true
@@ -19362,64 +18932,14 @@
"optional": true
},
"@schematics/angular": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-19.2.12.tgz",
- "integrity": "sha512-6S6tclFctLrjMvhpi8eVvswIpXqlybRpZLCTWyVeWIC6PHYLEyFmFoOhuhcSmOdtnwudvzOt6xWnWEVb3qXZbQ==",
+ "version": "19.2.13",
+ "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-19.2.13.tgz",
+ "integrity": "sha512-SOpK4AwH0isXo7Y2SkgXLyGLMw4GxWPAun6sCLiprmop4KlqKGGALn4xIW0yjq0s5GS0Vx0FFjz8bBfPkgnawA==",
"dev": true,
"requires": {
- "@angular-devkit/core": "19.2.12",
- "@angular-devkit/schematics": "19.2.12",
+ "@angular-devkit/core": "19.2.13",
+ "@angular-devkit/schematics": "19.2.13",
"jsonc-parser": "3.3.1"
- },
- "dependencies": {
- "@angular-devkit/core": {
- "version": "19.2.12",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.12.tgz",
- "integrity": "sha512-v5pdfZHZ8MTZozfpkhKoPFBpXQW+2GFbTfdyis8FBtevJWCbIsCR3xhodgI4jwzkSEAraN4oVtWvSytdNyBC6A==",
- "dev": true,
- "requires": {
- "ajv": "8.17.1",
- "ajv-formats": "3.0.1",
- "jsonc-parser": "3.3.1",
- "picomatch": "4.0.2",
- "rxjs": "7.8.1",
- "source-map": "0.7.4"
- }
- },
- "ajv-formats": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
- "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
- "dev": true,
- "requires": {
- "ajv": "^8.0.0"
- }
- },
- "chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
- "optional": true,
- "peer": true,
- "requires": {
- "readdirp": "^4.0.1"
- }
- },
- "picomatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
- "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
- "dev": true
- },
- "readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
- "optional": true,
- "peer": true
- }
}
},
"@sigstore/bundle": {
@@ -19606,9 +19126,9 @@
"dev": true
},
"@types/express": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
- "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
+ "version": "4.17.22",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.22.tgz",
+ "integrity": "sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w==",
"dev": true,
"requires": {
"@types/body-parser": "*",
@@ -19659,9 +19179,9 @@
}
},
"@types/jasmine": {
- "version": "4.3.6",
- "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.3.6.tgz",
- "integrity": "sha512-3N0FpQTeiWjm+Oo1WUYWguUS7E6JLceiGTriFrG8k5PU7zRLJCzLcWURU3wjMbZGS//a2/LgjsnO3QxIlwxt9g==",
+ "version": "5.1.8",
+ "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.8.tgz",
+ "integrity": "sha512-u7/CnvRdh6AaaIzYjCgUuVbREFgulhX05Qtf6ZtW+aOcjCKKVvKgpkPYJBFTZSHtFBYimzU4zP0V2vrEsq9Wcg==",
"dev": true
},
"@types/json-schema": {
@@ -19695,9 +19215,9 @@
}
},
"@types/qs": {
- "version": "6.9.18",
- "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz",
- "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==",
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
"dev": true
},
"@types/range-parser": {
@@ -19892,12 +19412,6 @@
}
}
},
- "@ungap/structured-clone": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
- "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
- "dev": true
- },
"@vitejs/plugin-basic-ssl": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.2.0.tgz",
@@ -20211,6 +19725,12 @@
"picomatch": "^2.0.4"
}
},
+ "argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
"aria-query": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
@@ -20612,19 +20132,12 @@
"dev": true
},
"chokidar": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
- "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"requires": {
- "anymatch": "~3.1.2",
- "braces": "~3.0.2",
- "fsevents": "~2.3.2",
- "glob-parent": "~5.1.2",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.6.0"
+ "readdirp": "^4.0.1"
}
},
"chownr": {
@@ -20966,23 +20479,6 @@
"import-fresh": "^3.3.0",
"js-yaml": "^4.1.0",
"parse-json": "^5.2.0"
- },
- "dependencies": {
- "argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true
- },
- "js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
- "requires": {
- "argparse": "^2.0.1"
- }
- }
}
},
"cross-spawn": {
@@ -21157,15 +20653,6 @@
"@leichtgewicht/ip-codec": "^2.0.1"
}
},
- "doctrine": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
- "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
- "dev": true,
- "requires": {
- "esutils": "^2.0.2"
- }
- },
"dom-serialize": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz",
@@ -21441,49 +20928,46 @@
"dev": true
},
"eslint": {
- "version": "8.57.1",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
- "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+ "version": "9.27.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz",
+ "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==",
"dev": true,
"requires": {
"@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.6.1",
- "@eslint/eslintrc": "^2.1.4",
- "@eslint/js": "8.57.1",
- "@humanwhocodes/config-array": "^0.13.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.20.0",
+ "@eslint/config-helpers": "^0.2.1",
+ "@eslint/core": "^0.14.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.27.0",
+ "@eslint/plugin-kit": "^0.3.1",
+ "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
- "@nodelib/fs.walk": "^1.2.8",
- "@ungap/structured-clone": "^1.2.0",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "@types/json-schema": "^7.0.15",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
- "cross-spawn": "^7.0.2",
+ "cross-spawn": "^7.0.6",
"debug": "^4.3.2",
- "doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^7.2.2",
- "eslint-visitor-keys": "^3.4.3",
- "espree": "^9.6.1",
- "esquery": "^1.4.2",
+ "eslint-scope": "^8.3.0",
+ "eslint-visitor-keys": "^4.2.0",
+ "espree": "^10.3.0",
+ "esquery": "^1.5.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^6.0.1",
+ "file-entry-cache": "^8.0.0",
"find-up": "^5.0.0",
"glob-parent": "^6.0.2",
- "globals": "^13.19.0",
- "graphemer": "^1.4.0",
"ignore": "^5.2.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
- "is-path-inside": "^3.0.3",
- "js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
- "levn": "^0.4.1",
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
- "optionator": "^0.9.3",
- "strip-ansi": "^6.0.1",
- "text-table": "^0.2.0"
+ "optionator": "^0.9.3"
},
"dependencies": {
"ajv": {
@@ -21498,12 +20982,6 @@
"uri-js": "^4.2.2"
}
},
- "argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true
- },
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -21511,15 +20989,21 @@
"dev": true
},
"eslint-scope": {
- "version": "7.2.2",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
- "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
+ "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
"dev": true,
"requires": {
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
}
},
+ "eslint-visitor-keys": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "dev": true
+ },
"find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -21539,24 +21023,6 @@
"is-glob": "^4.0.3"
}
},
- "globals": {
- "version": "13.24.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
- "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
- "dev": true,
- "requires": {
- "type-fest": "^0.20.2"
- }
- },
- "js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
- "requires": {
- "argparse": "^2.0.1"
- }
- },
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -21590,12 +21056,6 @@
"p-limit": "^3.0.2"
}
},
- "type-fest": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
- "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
- "dev": true
- },
"yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -21629,14 +21089,22 @@
"dev": true
},
"espree": {
- "version": "9.6.1",
- "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
- "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
+ "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
"dev": true,
"requires": {
- "acorn": "^8.9.0",
+ "acorn": "^8.14.0",
"acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^3.4.1"
+ "eslint-visitor-keys": "^4.2.0"
+ },
+ "dependencies": {
+ "eslint-visitor-keys": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "dev": true
+ }
}
},
"esquery": {
@@ -21855,12 +21323,12 @@
}
},
"file-entry-cache": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
- "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
"dev": true,
"requires": {
- "flat-cache": "^3.0.4"
+ "flat-cache": "^4.0.0"
}
},
"fill-range": {
@@ -21948,14 +21416,13 @@
"dev": true
},
"flat-cache": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
- "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
"dev": true,
"requires": {
"flatted": "^3.2.9",
- "keyv": "^4.5.3",
- "rimraf": "^3.0.2"
+ "keyv": "^4.5.4"
}
},
"flatted": {
@@ -22589,12 +22056,6 @@
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
- "is-path-inside": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
- "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
- "dev": true
- },
"is-plain-obj": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz",
@@ -22730,9 +22191,9 @@
}
},
"jasmine-core": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.1.tgz",
- "integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==",
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.7.1.tgz",
+ "integrity": "sha512-QnurrtpKsPoixxG2R3d1xP0St/2kcX5oTZyDyQJMY+Vzi/HUlu1kGm+2V8Tz+9lV991leB1l0xcsyz40s9xOOw==",
"dev": true
},
"jest-worker": {
@@ -22769,6 +22230,15 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
},
+ "js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "requires": {
+ "argparse": "^2.0.1"
+ }
+ },
"jsbn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
@@ -22864,6 +22334,22 @@
"yargs": "^16.1.1"
},
"dependencies": {
+ "chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "requires": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "fsevents": "~2.3.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ }
+ },
"cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -22875,6 +22361,15 @@
"wrap-ansi": "^7.0.0"
}
},
+ "readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "requires": {
+ "picomatch": "^2.2.1"
+ }
+ },
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -22940,6 +22435,14 @@
"dev": true,
"requires": {
"jasmine-core": "^4.1.0"
+ },
+ "dependencies": {
+ "jasmine-core": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.1.tgz",
+ "integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==",
+ "dev": true
+ }
}
},
"karma-jasmine-html-reporter": {
@@ -23399,9 +22902,9 @@
"dev": true
},
"memfs": {
- "version": "4.17.1",
- "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.1.tgz",
- "integrity": "sha512-thuTRd7F4m4dReCIy7vv4eNYnU6XI/tHMLSMMHLiortw/Y0QxqKtinG523U2aerzwYWGi606oBP4oMPy4+edag==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz",
+ "integrity": "sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==",
"dev": true,
"requires": {
"@jsonjoy.com/json-pack": "^1.0.3",
@@ -23644,9 +23147,9 @@
"dev": true
},
"msgpackr": {
- "version": "1.11.2",
- "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.2.tgz",
- "integrity": "sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==",
+ "version": "1.11.4",
+ "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.4.tgz",
+ "integrity": "sha512-uaff7RG9VIC4jacFW9xzL3jc0iM32DNHe4jYVycBcjUePT/Klnfj7pqtWJt9khvDFizmjN2TlYniYmSS2LIaZg==",
"dev": true,
"optional": true,
"requires": {
@@ -24143,7 +23646,6 @@
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
- "dev": true,
"requires": {
"entities": "^6.0.0"
},
@@ -24151,8 +23653,7 @@
"entities": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
- "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
- "dev": true
+ "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw=="
}
}
},
@@ -24354,9 +23855,9 @@
"dev": true
},
"prettier": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
- "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
+ "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true
},
"proc-log": {
@@ -24472,13 +23973,10 @@
}
},
"readdirp": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
- "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "dev": true,
- "requires": {
- "picomatch": "^2.2.1"
- }
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "dev": true
},
"reflect-metadata": {
"version": "0.2.2",
@@ -24701,9 +24199,9 @@
}
},
"rxjs": {
- "version": "7.8.1",
- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
- "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
+ "version": "7.8.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
+ "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
"requires": {
"tslib": "^2.1.0"
}
@@ -24730,23 +24228,6 @@
"chokidar": "^4.0.0",
"immutable": "^5.0.2",
"source-map-js": ">=0.6.2 <2.0.0"
- },
- "dependencies": {
- "chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
- "requires": {
- "readdirp": "^4.0.1"
- }
- },
- "readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true
- }
}
},
"sass-loader": {
@@ -25460,12 +24941,6 @@
"terser": "^5.31.1"
}
},
- "text-table": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
- "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
- "dev": true
- },
"thingies": {
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz",
@@ -25529,9 +25004,9 @@
"dev": true
},
"tree-dump": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz",
- "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.3.tgz",
+ "integrity": "sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg==",
"dev": true,
"requires": {}
},
@@ -25601,6 +25076,18 @@
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true
},
+ "typescript-eslint": {
+ "version": "8.32.1",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.32.1.tgz",
+ "integrity": "sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "@typescript-eslint/eslint-plugin": "8.32.1",
+ "@typescript-eslint/parser": "8.32.1",
+ "@typescript-eslint/utils": "8.32.1"
+ }
+ },
"ua-parser-js": {
"version": "0.7.39",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.39.tgz",
@@ -25761,153 +25248,153 @@
},
"dependencies": {
"@rollup/rollup-android-arm-eabi": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.2.tgz",
- "integrity": "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz",
+ "integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==",
"dev": true,
"optional": true,
"peer": true
},
"@rollup/rollup-android-arm64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.2.tgz",
- "integrity": "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz",
+ "integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==",
"dev": true,
"optional": true,
"peer": true
},
"@rollup/rollup-darwin-arm64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.2.tgz",
- "integrity": "sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz",
+ "integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==",
"dev": true,
"optional": true,
"peer": true
},
"@rollup/rollup-darwin-x64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.2.tgz",
- "integrity": "sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz",
+ "integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==",
"dev": true,
"optional": true,
"peer": true
},
"@rollup/rollup-freebsd-arm64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.2.tgz",
- "integrity": "sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz",
+ "integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==",
"dev": true,
"optional": true,
"peer": true
},
"@rollup/rollup-freebsd-x64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.2.tgz",
- "integrity": "sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz",
+ "integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==",
"dev": true,
"optional": true,
"peer": true
},
"@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.2.tgz",
- "integrity": "sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz",
+ "integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==",
"dev": true,
"optional": true,
"peer": true
},
"@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.2.tgz",
- "integrity": "sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz",
+ "integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==",
"dev": true,
"optional": true,
"peer": true
},
"@rollup/rollup-linux-arm64-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.2.tgz",
- "integrity": "sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz",
+ "integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==",
"dev": true,
"optional": true,
"peer": true
},
"@rollup/rollup-linux-arm64-musl": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.2.tgz",
- "integrity": "sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz",
+ "integrity": "sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==",
"dev": true,
"optional": true,
"peer": true
},
"@rollup/rollup-linux-loongarch64-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.2.tgz",
- "integrity": "sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz",
+ "integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==",
"dev": true,
"optional": true,
"peer": true
},
"@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.2.tgz",
- "integrity": "sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz",
+ "integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==",
"dev": true,
"optional": true,
"peer": true
},
"@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.2.tgz",
- "integrity": "sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz",
+ "integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==",
"dev": true,
"optional": true,
"peer": true
},
"@rollup/rollup-linux-s390x-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.2.tgz",
- "integrity": "sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz",
+ "integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==",
"dev": true,
"optional": true,
"peer": true
},
"@rollup/rollup-linux-x64-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.2.tgz",
- "integrity": "sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz",
+ "integrity": "sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==",
"dev": true,
"optional": true,
"peer": true
},
"@rollup/rollup-linux-x64-musl": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.2.tgz",
- "integrity": "sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.0.tgz",
+ "integrity": "sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==",
"dev": true,
"optional": true,
"peer": true
},
"@rollup/rollup-win32-arm64-msvc": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.2.tgz",
- "integrity": "sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz",
+ "integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==",
"dev": true,
"optional": true,
"peer": true
},
"@rollup/rollup-win32-ia32-msvc": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.2.tgz",
- "integrity": "sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz",
+ "integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==",
"dev": true,
"optional": true,
"peer": true
},
"@rollup/rollup-win32-x64-msvc": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.2.tgz",
- "integrity": "sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz",
+ "integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==",
"dev": true,
"optional": true,
"peer": true
@@ -25928,32 +25415,32 @@
"peer": true
},
"rollup": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.2.tgz",
- "integrity": "sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.0.tgz",
+ "integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==",
"dev": true,
"peer": true,
"requires": {
- "@rollup/rollup-android-arm-eabi": "4.40.2",
- "@rollup/rollup-android-arm64": "4.40.2",
- "@rollup/rollup-darwin-arm64": "4.40.2",
- "@rollup/rollup-darwin-x64": "4.40.2",
- "@rollup/rollup-freebsd-arm64": "4.40.2",
- "@rollup/rollup-freebsd-x64": "4.40.2",
- "@rollup/rollup-linux-arm-gnueabihf": "4.40.2",
- "@rollup/rollup-linux-arm-musleabihf": "4.40.2",
- "@rollup/rollup-linux-arm64-gnu": "4.40.2",
- "@rollup/rollup-linux-arm64-musl": "4.40.2",
- "@rollup/rollup-linux-loongarch64-gnu": "4.40.2",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.40.2",
- "@rollup/rollup-linux-riscv64-gnu": "4.40.2",
- "@rollup/rollup-linux-riscv64-musl": "4.40.2",
- "@rollup/rollup-linux-s390x-gnu": "4.40.2",
- "@rollup/rollup-linux-x64-gnu": "4.40.2",
- "@rollup/rollup-linux-x64-musl": "4.40.2",
- "@rollup/rollup-win32-arm64-msvc": "4.40.2",
- "@rollup/rollup-win32-ia32-msvc": "4.40.2",
- "@rollup/rollup-win32-x64-msvc": "4.40.2",
+ "@rollup/rollup-android-arm-eabi": "4.41.0",
+ "@rollup/rollup-android-arm64": "4.41.0",
+ "@rollup/rollup-darwin-arm64": "4.41.0",
+ "@rollup/rollup-darwin-x64": "4.41.0",
+ "@rollup/rollup-freebsd-arm64": "4.41.0",
+ "@rollup/rollup-freebsd-x64": "4.41.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.41.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.41.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.41.0",
+ "@rollup/rollup-linux-arm64-musl": "4.41.0",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.41.0",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.41.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.41.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.41.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.41.0",
+ "@rollup/rollup-linux-x64-gnu": "4.41.0",
+ "@rollup/rollup-linux-x64-musl": "4.41.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.41.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.41.0",
+ "@rollup/rollup-win32-x64-msvc": "4.41.0",
"@types/estree": "1.0.7",
"fsevents": "~2.3.2"
}
@@ -26081,6 +25568,22 @@
"ws": "^8.18.0"
},
"dependencies": {
+ "chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "requires": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "fsevents": "~2.3.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ }
+ },
"http-proxy-middleware": {
"version": "2.0.9",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz",
@@ -26093,6 +25596,15 @@
"is-plain-obj": "^3.0.0",
"micromatch": "^4.0.2"
}
+ },
+ "readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "requires": {
+ "picomatch": "^2.2.1"
+ }
}
}
},
diff --git a/package.json b/package.json
index 6584917..cadafea 100644
--- a/package.json
+++ b/package.json
@@ -11,38 +11,50 @@
},
"private": true,
"dependencies": {
- "@angular/animations": "^19.2.10",
- "@angular/common": "^19.2.10",
- "@angular/compiler": "^19.2.10",
- "@angular/core": "^19.2.10",
- "@angular/forms": "^19.2.10",
- "@angular/platform-browser": "^19.2.10",
- "@angular/platform-browser-dynamic": "^19.2.10",
- "@angular/router": "^19.2.10",
- "rxjs": "~7.8.0",
+ "@angular/animations": "^19.2.12",
+ "@angular/cdk": "^19.2.17",
+ "@angular/common": "^19.2.12",
+ "@angular/compiler": "^19.2.12",
+ "@angular/core": "^19.2.12",
+ "@angular/forms": "^19.2.12",
+ "@angular/material": "^19.2.17",
+ "@angular/platform-browser": "^19.2.12",
+ "@angular/platform-browser-dynamic": "^19.2.12",
+ "@angular/router": "^19.2.12",
+ "@ngrx/effects": "^19.2.0",
+ "@ngrx/entity": "^19.2.0",
+ "@ngrx/operators": "^19.2.0",
+ "@ngrx/router-store": "^19.2.0",
+ "@ngrx/store": "^19.2.0",
+ "@ngrx/store-devtools": "^19.2.0",
+ "rxjs": "^7.8.2",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
},
"devDependencies": {
- "@angular-devkit/build-angular": "^19.2.12",
+ "@angular-devkit/build-angular": "^19.2.13",
+ "@angular-devkit/core": "^19.2.13",
"@angular-eslint/builder": "19.4.0",
"@angular-eslint/eslint-plugin": "19.4.0",
"@angular-eslint/eslint-plugin-template": "19.4.0",
"@angular-eslint/schematics": "19.4.0",
"@angular-eslint/template-parser": "19.4.0",
- "@angular/cli": "^19.2.12",
- "@angular/compiler-cli": "^19.2.10",
- "@types/jasmine": "~4.3.0",
+ "@angular/cli": "^19.2.13",
+ "@angular/compiler-cli": "^19.2.12",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "^9.27.0",
+ "@ngrx/eslint-plugin": "^19.2.0",
+ "@types/jasmine": "^5.1.8",
"@typescript-eslint/eslint-plugin": "8.32.1",
"@typescript-eslint/parser": "8.32.1",
- "eslint": "^8.51.0",
- "jasmine-core": "~4.6.0",
+ "eslint": "^9.27.0",
+ "jasmine-core": "^5.7.1",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
- "prettier": "^3.3.3",
+ "prettier": "^3.5.3",
"typescript": "~5.8.3"
}
-}
+}
\ No newline at end of file
diff --git a/src/app/app-routes.ts b/src/app/app-routes.ts
index a1cf04d..ba27816 100644
--- a/src/app/app-routes.ts
+++ b/src/app/app-routes.ts
@@ -1,3 +1,41 @@
import { Routes } from '@angular/router';
+import { provideEffects } from '@ngrx/effects';
+import { provideState } from '@ngrx/store';
+import { GuestBookService } from './guest-book/guest-book.service';
+import { GuestBookEffects } from './guest-book/state/guest-book.effects';
+import { guestBookFeature } from './guest-book/state/guest-book.reducer';
+import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
+import { PostsService } from './posts/posts.service';
+import { PostsEffects } from './posts/state/posts.effects';
+import { postsFeature } from './posts/state/posts.reducer';
+import { UsersEffects } from './users/state/users.effects';
+import { usersFeature } from './users/state/users.reducer';
-export const appRoutes: Routes = [];
+export const appRoutes: Routes = [
+ { path: '', redirectTo: '/posts', pathMatch: 'full' },
+ {
+ path: 'posts',
+ loadChildren: () =>
+ import('./posts/posts.routes').then((mod) => mod.routes),
+ providers: [
+ PostsService,
+ provideState(postsFeature),
+ provideState(usersFeature),
+ provideEffects(PostsEffects, UsersEffects),
+ ],
+ },
+ {
+ path: 'guest-book',
+ loadChildren: () =>
+ import('./guest-book/guest-book.routes').then((mod) => mod.routes),
+ providers: [
+ GuestBookService,
+ provideState(guestBookFeature),
+ provideEffects(GuestBookEffects),
+ ],
+ },
+ {
+ path: '**',
+ component: PageNotFoundComponent,
+ },
+];
diff --git a/src/app/app.component.html b/src/app/app.component.html
index f28834e..d47bbd3 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,488 +1,29 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
{{ title }} app is running!
-
-
-
-
-
-
-
Resources
-
Here are some links to help you get started:
-
-
-
-
-
Next Steps
-
What do you want to do next with your app?
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-@switch (selection.value) {
- @default {
-
ng generate component xyz
-}
- @case ('material') {
-
ng add @angular/material
-}
- @case ('pwa') {
-
ng add @angular/pwa
-}
- @case ('dependency') {
-
ng add _____
-}
- @case ('test') {
-
ng test
-}
- @case ('build') {
-
ng build
-}
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/app.component.scss b/src/app/app.component.scss
index e69de29..cf8500a 100644
--- a/src/app/app.component.scss
+++ b/src/app/app.component.scss
@@ -0,0 +1,26 @@
+.main {
+ padding: 16px;
+ margin-right: 300px;
+}
+
+.sidenav {
+ width: 300px;
+ padding: 8px;
+ padding: 8px 0 8px 8px;
+
+ .links {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+
+ a {
+ display: flex;
+ justify-content: flex-start;
+
+ &.active {
+ background-color: var(--mat-sys-primary);
+ color: var(--mat-sys-surface);
+ }
+ }
+ }
+}
diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts
deleted file mode 100644
index a59d261..0000000
--- a/src/app/app.component.spec.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { TestBed } from '@angular/core/testing';
-import { RouterTestingModule } from '@angular/router/testing';
-import { AppComponent } from './app.component';
-
-describe('AppComponent', () => {
- beforeEach(() => TestBed.configureTestingModule({
- imports: [RouterTestingModule, AppComponent]
-}));
-
- it('should create the app', () => {
- const fixture = TestBed.createComponent(AppComponent);
- const app = fixture.componentInstance;
- expect(app).toBeTruthy();
- });
-
- it(`should have as title 'angular-template'`, () => {
- const fixture = TestBed.createComponent(AppComponent);
- const app = fixture.componentInstance;
- expect(app.title).toEqual('angular-template');
- });
-
- it('should render title', () => {
- const fixture = TestBed.createComponent(AppComponent);
- fixture.detectChanges();
- const compiled = fixture.nativeElement as HTMLElement;
- expect(compiled.querySelector('.content span')?.textContent).toContain('angular-template app is running!');
- });
-});
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index c53dbc9..6711b35 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,13 +1,35 @@
-
import { Component } from '@angular/core';
-import { RouterOutlet } from '@angular/router';
+import { MatButtonModule } from '@angular/material/button';
+import { MatIcon } from '@angular/material/icon';
+import {
+ MatSidenav,
+ MatSidenavContainer,
+ MatSidenavContent,
+} from '@angular/material/sidenav';
+import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
- imports: [RouterOutlet],
+ imports: [
+ RouterOutlet,
+ RouterLink,
+ RouterLinkActive,
+ MatButtonModule,
+ MatSidenavContainer,
+ MatSidenav,
+ MatSidenavContent,
+ MatIcon,
+ ],
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
- title = 'angular-template';
+ readonly routes = [
+ {
+ url: '/posts',
+ title: 'Posts',
+ icon: 'forum',
+ },
+ { url: '/guest-book', title: 'Guest Book', icon: 'book' },
+ ];
}
diff --git a/src/app/guest-book/guest-book-author-dialog/guest-book-author-dialog.component.html b/src/app/guest-book/guest-book-author-dialog/guest-book-author-dialog.component.html
new file mode 100644
index 0000000..0f2ef14
--- /dev/null
+++ b/src/app/guest-book/guest-book-author-dialog/guest-book-author-dialog.component.html
@@ -0,0 +1,16 @@
+
+
![Avatar]()
+
+
{{ data.author.name }}
+ {{ data.author.email }}
+
+
+
+ Messages
+ @for (entry of data.entries; track entry.id) {
+ {{ entry.message }}
+ }
+
+
+
+
diff --git a/src/app/guest-book/guest-book-author-dialog/guest-book-author-dialog.component.scss b/src/app/guest-book/guest-book-author-dialog/guest-book-author-dialog.component.scss
new file mode 100644
index 0000000..5fffdc5
--- /dev/null
+++ b/src/app/guest-book/guest-book-author-dialog/guest-book-author-dialog.component.scss
@@ -0,0 +1,27 @@
+.author {
+ display: flex;
+ align-items: end;
+
+ img {
+ border-radius: 50%;
+ width: 64px;
+ height: 64px;
+ }
+
+ .contacts {
+ margin-left: 16px;
+ display: flex;
+ flex-direction: column;
+
+ h2 {
+ padding: 0;
+ margin: 0;
+ line-height: normal;
+ font-size: 20px;
+ }
+
+ .email {
+ font-size: 14px;
+ }
+ }
+}
diff --git a/src/app/guest-book/guest-book-author-dialog/guest-book-author-dialog.component.ts b/src/app/guest-book/guest-book-author-dialog/guest-book-author-dialog.component.ts
new file mode 100644
index 0000000..b194990
--- /dev/null
+++ b/src/app/guest-book/guest-book-author-dialog/guest-book-author-dialog.component.ts
@@ -0,0 +1,25 @@
+import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
+import { MatButtonModule } from '@angular/material/button';
+import {
+ MAT_DIALOG_DATA,
+ MatDialogModule,
+ MatDialogTitle,
+} from '@angular/material/dialog';
+import { GuestBookAuthor, GuestBookEntry } from '../guest-book.model';
+
+export interface GuestBookAuthorDialogData {
+ author: GuestBookAuthor;
+ avatarUrl: string;
+ entries: GuestBookEntry[];
+}
+
+@Component({
+ selector: 'app-guest-book-author-dialog',
+ imports: [MatDialogModule, MatButtonModule, MatDialogTitle],
+ templateUrl: './guest-book-author-dialog.component.html',
+ styleUrl: './guest-book-author-dialog.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class GuestBookAuthorDialogComponent {
+ readonly data = inject(MAT_DIALOG_DATA);
+}
diff --git a/src/app/guest-book/guest-book-form/guest-book-form.component.html b/src/app/guest-book/guest-book-form/guest-book-form.component.html
new file mode 100644
index 0000000..0bab8ba
--- /dev/null
+++ b/src/app/guest-book/guest-book-form/guest-book-form.component.html
@@ -0,0 +1,44 @@
+
+
+
diff --git a/src/app/guest-book/guest-book-form/guest-book-form.component.scss b/src/app/guest-book/guest-book-form/guest-book-form.component.scss
new file mode 100644
index 0000000..7f80aaa
--- /dev/null
+++ b/src/app/guest-book/guest-book-form/guest-book-form.component.scss
@@ -0,0 +1,14 @@
+.form-card {
+ padding: 16px;
+ form {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+
+ .group {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
+ }
+}
diff --git a/src/app/guest-book/guest-book-form/guest-book-form.component.spec.ts b/src/app/guest-book/guest-book-form/guest-book-form.component.spec.ts
new file mode 100644
index 0000000..eb028c9
--- /dev/null
+++ b/src/app/guest-book/guest-book-form/guest-book-form.component.spec.ts
@@ -0,0 +1,85 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { GuestBookEntry } from '../guest-book.model';
+import { GuestBookFormComponent } from './guest-book-form.component';
+
+describe('GuestBookFormComponent', () => {
+ let component: GuestBookFormComponent;
+ let fixture: ComponentFixture;
+
+ let DEFAULT_ENTRY: GuestBookEntry;
+
+ beforeEach(async () => {
+ TestBed.configureTestingModule({
+ imports: [GuestBookFormComponent],
+ });
+
+ fixture = TestBed.createComponent(GuestBookFormComponent);
+ component = fixture.componentInstance;
+
+ DEFAULT_ENTRY = {
+ author: {
+ name: 'Author Name',
+ email: 'test@example.com',
+ },
+ message: 'a'.repeat(20),
+ };
+ });
+
+ describe('onSubmit', () => {
+ it('should not emit when form is invalid', () => {
+ spyOn(component.formSubmitted, 'emit');
+
+ component.onSubmit();
+
+ expect(component.formSubmitted.emit).not.toHaveBeenCalled();
+ });
+
+ it('should emit the formSubmitted output with the expected entry', () => {
+ component.form.setValue(DEFAULT_ENTRY);
+ spyOn(component.formSubmitted, 'emit');
+
+ component.onSubmit();
+
+ expect(component.formSubmitted.emit).toHaveBeenCalledWith(DEFAULT_ENTRY);
+ });
+
+ it('should reset form value and errors', () => {
+ component.form.setValue(DEFAULT_ENTRY);
+ spyOn(component.formSubmitted, 'emit');
+
+ component.onSubmit();
+
+ expect(component.form.getRawValue()).toEqual({
+ author: {
+ name: '',
+ email: '',
+ },
+ message: '',
+ });
+ expect(component.name.errors).toBeNull();
+ expect(component.email.errors).toBeNull();
+ expect(component.message.errors).toBeNull();
+ });
+ });
+
+ describe('validation', () => {
+ it('should validate required fields', () => {
+ const requiredError = { required: true };
+ expect(component.name.errors).toEqual(requiredError);
+ expect(component.email.errors).toEqual(requiredError);
+ expect(component.message.errors).toEqual(requiredError);
+ });
+
+ it('should validate email', () => {
+ component.email.setValue('Invalid Email Format');
+ expect(component.email.errors).toEqual({ email: true });
+ });
+
+ it('should validate message to be at least 20 characters', () => {
+ component.message.setValue('a'.repeat(19));
+ expect(component.message.errors).toEqual({
+ minlength: { requiredLength: 20, actualLength: 19 },
+ });
+ });
+ });
+});
diff --git a/src/app/guest-book/guest-book-form/guest-book-form.component.ts b/src/app/guest-book/guest-book-form/guest-book-form.component.ts
new file mode 100644
index 0000000..d2a1e05
--- /dev/null
+++ b/src/app/guest-book/guest-book-form/guest-book-form.component.ts
@@ -0,0 +1,67 @@
+import {
+ ChangeDetectionStrategy,
+ Component,
+ inject,
+ input,
+ output,
+} from '@angular/core';
+import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
+import { MatButton } from '@angular/material/button';
+import { MatCard } from '@angular/material/card';
+import { MatError, MatFormField, MatLabel } from '@angular/material/form-field';
+import { MatInput } from '@angular/material/input';
+import { GuestBookEntry } from '../guest-book.model';
+
+@Component({
+ selector: 'app-guest-book-form',
+ imports: [
+ ReactiveFormsModule,
+ MatFormField,
+ MatLabel,
+ MatInput,
+ MatButton,
+ MatError,
+ MatCard,
+ ],
+ templateUrl: './guest-book-form.component.html',
+ styleUrl: './guest-book-form.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class GuestBookFormComponent {
+ saving = input(false);
+ formSubmitted = output();
+
+ private fb = inject(FormBuilder);
+ form = this.fb.nonNullable.group({
+ author: this.fb.nonNullable.group({
+ name: ['', Validators.required],
+ email: ['', [Validators.required, Validators.email]],
+ }),
+ message: ['', [Validators.required, Validators.minLength(20)]],
+ });
+
+ public get name() {
+ return this.form.controls.author.controls.name;
+ }
+
+ public get email() {
+ return this.form.controls.author.controls.email;
+ }
+
+ public get message() {
+ return this.form.controls.message;
+ }
+
+ onSubmit() {
+ if (!this.form.valid) {
+ return;
+ }
+
+ this.formSubmitted.emit(this.form.getRawValue());
+
+ this.form.reset();
+ this.name.setErrors(null);
+ this.email.setErrors(null);
+ this.message.setErrors(null);
+ }
+}
diff --git a/src/app/guest-book/guest-book-list/guest-book-list.component.html b/src/app/guest-book/guest-book-list/guest-book-list.component.html
new file mode 100644
index 0000000..0949835
--- /dev/null
+++ b/src/app/guest-book/guest-book-list/guest-book-list.component.html
@@ -0,0 +1,15 @@
+
+
+
diff --git a/src/app/guest-book/guest-book-list/guest-book-list.component.scss b/src/app/guest-book/guest-book-list/guest-book-list.component.scss
new file mode 100644
index 0000000..14204dd
--- /dev/null
+++ b/src/app/guest-book/guest-book-list/guest-book-list.component.scss
@@ -0,0 +1,23 @@
+.list {
+ list-style-type: none;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ margin: 0;
+ padding: 16px;
+
+ .entry {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+
+ .content {
+ display: flex;
+ flex-direction: column;
+
+ .author {
+ font-size: 14px;
+ }
+ }
+ }
+}
diff --git a/src/app/guest-book/guest-book-list/guest-book-list.component.spec.ts b/src/app/guest-book/guest-book-list/guest-book-list.component.spec.ts
new file mode 100644
index 0000000..208d73f
--- /dev/null
+++ b/src/app/guest-book/guest-book-list/guest-book-list.component.spec.ts
@@ -0,0 +1,29 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { GuestBookAuthor } from '../guest-book.model';
+import { GuestBookListComponent } from './guest-book-list.component';
+
+describe('GuestBookListComponent', () => {
+ let component: GuestBookListComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ TestBed.configureTestingModule({
+ imports: [GuestBookListComponent],
+ });
+
+ fixture = TestBed.createComponent(GuestBookListComponent);
+ component = fixture.componentInstance;
+ });
+
+ it('should emit authorClicked with the provided author on click', () => {
+ const author: GuestBookAuthor = {
+ name: 'Test User',
+ email: 'test@example.com',
+ };
+ spyOn(component.authorClicked, 'emit');
+
+ component.onClick(author);
+
+ expect(component.authorClicked.emit).toHaveBeenCalledWith(author);
+ });
+});
diff --git a/src/app/guest-book/guest-book-list/guest-book-list.component.ts b/src/app/guest-book/guest-book-list/guest-book-list.component.ts
new file mode 100644
index 0000000..d8db4df
--- /dev/null
+++ b/src/app/guest-book/guest-book-list/guest-book-list.component.ts
@@ -0,0 +1,26 @@
+import {
+ ChangeDetectionStrategy,
+ Component,
+ input,
+ output,
+} from '@angular/core';
+import { MatIconButton } from '@angular/material/button';
+import { MatCard } from '@angular/material/card';
+import { MatIcon } from '@angular/material/icon';
+import { GuestBookAuthor, GuestBookEntry } from '../guest-book.model';
+
+@Component({
+ selector: 'app-guest-book-list',
+ imports: [MatCard, MatIcon, MatIconButton],
+ templateUrl: './guest-book-list.component.html',
+ styleUrl: './guest-book-list.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class GuestBookListComponent {
+ entries = input([]);
+ authorClicked = output();
+
+ onClick(author: GuestBookAuthor) {
+ this.authorClicked.emit(author);
+ }
+}
diff --git a/src/app/guest-book/guest-book-page/guest-book-page.component.html b/src/app/guest-book/guest-book-page/guest-book-page.component.html
new file mode 100644
index 0000000..0863b23
--- /dev/null
+++ b/src/app/guest-book/guest-book-page/guest-book-page.component.html
@@ -0,0 +1,9 @@
+
+
+ @if (!loading()) {
+
+ }
+
diff --git a/src/app/guest-book/guest-book-page/guest-book-page.component.scss b/src/app/guest-book/guest-book-page/guest-book-page.component.scss
new file mode 100644
index 0000000..df6c65e
--- /dev/null
+++ b/src/app/guest-book/guest-book-page/guest-book-page.component.scss
@@ -0,0 +1,5 @@
+.page {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
diff --git a/src/app/guest-book/guest-book-page/guest-book-page.component.spec.ts b/src/app/guest-book/guest-book-page/guest-book-page.component.spec.ts
new file mode 100644
index 0000000..13069b9
--- /dev/null
+++ b/src/app/guest-book/guest-book-page/guest-book-page.component.spec.ts
@@ -0,0 +1,157 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { MatDialog } from '@angular/material/dialog';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { of } from 'rxjs';
+import { UsersService } from 'src/app/users/users.service';
+import { GuestBookAuthorDialogComponent } from '../guest-book-author-dialog/guest-book-author-dialog.component';
+import { GuestBookAuthor, GuestBookEntry } from '../guest-book.model';
+import { GuestBookPageActions } from '../state/guest-book.actions';
+import {
+ selectGuestBookEntries,
+ selectGuestBookEntriesLoading,
+ selectGuestBookEntrySaving,
+} from '../state/guest-book.selectors';
+import { GuestBookPageComponent } from './guest-book-page.component';
+
+describe('GuestBookPageComponent', () => {
+ let mockUsersService: jasmine.SpyObj;
+ let mockDialog: jasmine.SpyObj;
+ let dispatchSpy: jasmine.Spy;
+
+ let store: MockStore;
+ let component: GuestBookPageComponent;
+ let fixture: ComponentFixture;
+
+ const USER_ENTRIES = [
+ {
+ id: 1,
+ author: {
+ name: 'User 1',
+ email: 'user1@example.com',
+ },
+ message: 'Message 1',
+ },
+ {
+ id: 3,
+ author: {
+ name: 'User 1',
+ email: 'user1@example.com',
+ },
+ message: 'Message 3',
+ },
+ {
+ id: 4,
+ author: {
+ name: 'User 1',
+ email: 'user1@example.com',
+ },
+ message: 'Message 4',
+ },
+ ];
+
+ beforeEach(async () => {
+ mockUsersService = jasmine.createSpyObj(['getAvatarUrl']);
+ mockDialog = jasmine.createSpyObj(['open']);
+
+ TestBed.configureTestingModule({
+ imports: [GuestBookPageComponent],
+ providers: [
+ provideMockStore({
+ selectors: [
+ {
+ selector: selectGuestBookEntries,
+ value: [
+ {
+ id: 2,
+ author: {
+ name: 'User 2',
+ email: 'user2@example.com',
+ },
+ message: 'Message 2',
+ },
+ ...USER_ENTRIES,
+ {
+ id: 5,
+ author: {
+ name: 'User 2',
+ email: 'user2@example.com',
+ },
+ message: 'Message 5',
+ },
+ ] as GuestBookEntry[],
+ },
+ {
+ selector: selectGuestBookEntrySaving,
+ value: false,
+ },
+ {
+ selector: selectGuestBookEntriesLoading,
+ value: false,
+ },
+ ],
+ }),
+ {
+ provide: UsersService,
+ useValue: mockUsersService,
+ },
+ {
+ provide: MatDialog,
+ useValue: mockDialog,
+ },
+ ],
+ });
+
+ fixture = TestBed.createComponent(GuestBookPageComponent);
+ component = fixture.componentInstance;
+ store = TestBed.inject(MockStore);
+ dispatchSpy = spyOn(store, 'dispatch');
+ });
+
+ it('should dispatch page opened action on init', () => {
+ component.ngOnInit();
+
+ expect(dispatchSpy).toHaveBeenCalledOnceWith(
+ GuestBookPageActions.pageOpened(),
+ );
+ });
+
+ it('should dispatch entry saved action on submit', () => {
+ const guestBookEntry: GuestBookEntry = {
+ author: {
+ name: 'Author Name',
+ email: 'test@example.com',
+ },
+ message: 'Test message',
+ };
+
+ component.onSubmit(guestBookEntry);
+
+ expect(dispatchSpy).toHaveBeenCalledOnceWith(
+ GuestBookPageActions.entrySaved({ entry: guestBookEntry }),
+ );
+ });
+
+ it('should open dialog with expected data on author click', () => {
+ const author: GuestBookAuthor = {
+ name: 'User 1',
+ email: 'user1@example.com',
+ };
+
+ const avatarUrl = 'http://example.com';
+ mockUsersService.getAvatarUrl.and.returnValue(of(avatarUrl));
+
+ component.onAuthorClick(author);
+ fixture.detectChanges();
+
+ expect(mockDialog.open).toHaveBeenCalledOnceWith(
+ GuestBookAuthorDialogComponent,
+ {
+ data: {
+ author,
+ avatarUrl,
+ entries: USER_ENTRIES,
+ },
+ },
+ );
+ });
+});
diff --git a/src/app/guest-book/guest-book-page/guest-book-page.component.ts b/src/app/guest-book/guest-book-page/guest-book-page.component.ts
new file mode 100644
index 0000000..ef1fbcc
--- /dev/null
+++ b/src/app/guest-book/guest-book-page/guest-book-page.component.ts
@@ -0,0 +1,57 @@
+import { Component, inject, OnInit } from '@angular/core';
+import { MatDialog } from '@angular/material/dialog';
+import { Store } from '@ngrx/store';
+import { UsersService } from 'src/app/users/users.service';
+import {
+ GuestBookAuthorDialogComponent,
+ GuestBookAuthorDialogData,
+} from '../guest-book-author-dialog/guest-book-author-dialog.component';
+import { GuestBookFormComponent } from '../guest-book-form/guest-book-form.component';
+import { GuestBookListComponent } from '../guest-book-list/guest-book-list.component';
+import { GuestBookAuthor, GuestBookEntry } from '../guest-book.model';
+import { GuestBookPageActions } from '../state/guest-book.actions';
+import {
+ selectGuestBookEntries,
+ selectGuestBookEntriesLoading,
+ selectGuestBookEntrySaving,
+} from '../state/guest-book.selectors';
+
+@Component({
+ selector: 'app-guest-book-page',
+ imports: [GuestBookFormComponent, GuestBookListComponent],
+ templateUrl: './guest-book-page.component.html',
+ styleUrl: './guest-book-page.component.scss',
+})
+export class GuestBookPageComponent implements OnInit {
+ private store = inject(Store);
+ private usersService = inject(UsersService);
+ private dialog = inject(MatDialog);
+
+ entries = this.store.selectSignal(selectGuestBookEntries);
+ loading = this.store.selectSignal(selectGuestBookEntriesLoading);
+ saving = this.store.selectSignal(selectGuestBookEntrySaving);
+
+ ngOnInit(): void {
+ this.store.dispatch(GuestBookPageActions.pageOpened());
+ }
+
+ onSubmit(guestBookEntry: GuestBookEntry) {
+ this.store.dispatch(
+ GuestBookPageActions.entrySaved({ entry: guestBookEntry }),
+ );
+ }
+
+ onAuthorClick(guestBookAuthor: GuestBookAuthor) {
+ this.usersService.getAvatarUrl(guestBookAuthor.email).subscribe((url) => {
+ this.dialog.open(GuestBookAuthorDialogComponent, {
+ data: {
+ author: guestBookAuthor,
+ avatarUrl: url,
+ entries: this.entries().filter(
+ (x) => x.author.email == guestBookAuthor.email,
+ ),
+ } as GuestBookAuthorDialogData,
+ });
+ });
+ }
+}
diff --git a/src/app/guest-book/guest-book.model.ts b/src/app/guest-book/guest-book.model.ts
new file mode 100644
index 0000000..ba7875e
--- /dev/null
+++ b/src/app/guest-book/guest-book.model.ts
@@ -0,0 +1,10 @@
+export interface GuestBookEntry {
+ id?: number;
+ author: GuestBookAuthor;
+ message: string;
+}
+
+export interface GuestBookAuthor {
+ name: string;
+ email: string;
+}
diff --git a/src/app/guest-book/guest-book.routes.ts b/src/app/guest-book/guest-book.routes.ts
new file mode 100644
index 0000000..88031ae
--- /dev/null
+++ b/src/app/guest-book/guest-book.routes.ts
@@ -0,0 +1,9 @@
+import { Routes } from '@angular/router';
+import { GuestBookPageComponent } from './guest-book-page/guest-book-page.component';
+
+export const routes: Routes = [
+ {
+ path: '',
+ component: GuestBookPageComponent,
+ },
+];
diff --git a/src/app/guest-book/guest-book.service.ts b/src/app/guest-book/guest-book.service.ts
new file mode 100644
index 0000000..a40d7a3
--- /dev/null
+++ b/src/app/guest-book/guest-book.service.ts
@@ -0,0 +1,113 @@
+import { Injectable } from '@angular/core';
+import { delay, Observable, of } from 'rxjs';
+import { GuestBookEntry } from './guest-book.model';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class GuestBookService {
+ private entries: GuestBookEntry[] = [
+ {
+ id: 1,
+ author: {
+ name: 'Ava Thompson',
+ email: 'ava.thompson@example.com',
+ },
+ message:
+ 'Praesent sapien massa, convallis a pellentesque nec, egestas non nisi.',
+ },
+ {
+ id: 2,
+ author: {
+ name: 'William Clark',
+ email: 'william.clark@example.com',
+ },
+ message:
+ 'Quisque velit nisi, pretium ut lacinia in, elementum id enim. Proin eget tortor risus.',
+ },
+ {
+ id: 3,
+ author: {
+ name: 'Dmitrijs Savkins',
+ email: 'dhmitry@gmail.com',
+ },
+ message:
+ 'Suspendisse potenti. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.',
+ },
+ {
+ id: 4,
+ author: {
+ name: 'William Clark',
+ email: 'william.clark@example.com',
+ },
+ message: 'Nulla quis lorem ut libero malesuada feugiat.',
+ },
+ {
+ id: 5,
+ author: {
+ name: 'James Hall',
+ email: 'james.hall@example.com',
+ },
+ message:
+ 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Donec velit neque, auctor sit amet aliquam vel, ullamcorper sit amet ligula.',
+ },
+ {
+ id: 6,
+ author: {
+ name: 'Isabella Walker',
+ email: 'isabella.walker@example.com',
+ },
+ message: 'Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a.',
+ },
+ {
+ id: 7,
+ author: {
+ name: 'Dmitrijs Savkins',
+ email: 'dhmitry@gmail.com',
+ },
+ message:
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ },
+ {
+ id: 8,
+ author: {
+ name: 'Dmitrijs Savkins',
+ email: 'dhmitry@gmail.com',
+ },
+ message:
+ 'Curabitur non nulla sit amet nisl tempus convallis quis ac lectus.',
+ },
+ {
+ id: 9,
+ author: {
+ name: 'Dmitrijs Savkins',
+ email: 'dhmitry@gmail.com',
+ },
+ message:
+ 'Vivamus suscipit tortor eget felis porttitor volutpat. Cras ultricies ligula sed magna dictum porta.',
+ },
+ {
+ id: 10,
+ author: {
+ name: 'Ava Thompson',
+ email: 'ava.thompson@example.com',
+ },
+ message:
+ 'Donec sollicitudin molestie malesuada. Quisque velit nisi, pretium ut lacinia in, elementum id enim. Nulla porttitor accumsan tincidunt.',
+ },
+ ];
+
+ getEntries(): Observable {
+ return of(this.entries).pipe(delay(500));
+ }
+
+ save(entry: GuestBookEntry): Observable {
+ const toSave: GuestBookEntry = {
+ ...entry,
+ id: Math.max(...this.entries.map((x) => x.id ?? 0)) + 1,
+ };
+
+ this.entries = [...this.entries, toSave];
+ return of(toSave).pipe(delay(500));
+ }
+}
diff --git a/src/app/guest-book/state/guest-book.actions.ts b/src/app/guest-book/state/guest-book.actions.ts
new file mode 100644
index 0000000..0ed6beb
--- /dev/null
+++ b/src/app/guest-book/state/guest-book.actions.ts
@@ -0,0 +1,20 @@
+import { createActionGroup, emptyProps, props } from '@ngrx/store';
+import { GuestBookEntry } from '../guest-book.model';
+
+export const GuestBookPageActions = createActionGroup({
+ source: 'Guest Book Page',
+ events: {
+ 'Page Opened': emptyProps(),
+ 'Entry Saved': props<{ entry: GuestBookEntry }>(),
+ },
+});
+
+export const GuestBookApiActions = createActionGroup({
+ source: 'Guest Book API',
+ events: {
+ 'Entries Loaded Success': props<{ entries: GuestBookEntry[] }>(),
+ 'Entries Loaded Fail': props<{ message: string }>(),
+ 'Entry Saved Success': props<{ entry: GuestBookEntry }>(),
+ 'Entry Saved Fail': props<{ message: string }>(),
+ },
+});
diff --git a/src/app/guest-book/state/guest-book.effects.spec.ts b/src/app/guest-book/state/guest-book.effects.spec.ts
new file mode 100644
index 0000000..c162d6c
--- /dev/null
+++ b/src/app/guest-book/state/guest-book.effects.spec.ts
@@ -0,0 +1,171 @@
+import { TestBed } from '@angular/core/testing';
+import { Actions } from '@ngrx/effects';
+import { provideMockActions } from '@ngrx/effects/testing';
+import { Action } from '@ngrx/store';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { Observable } from 'rxjs';
+import { TestScheduler } from 'rxjs/testing';
+import { GuestBookEntry } from '../guest-book.model';
+import { GuestBookService } from '../guest-book.service';
+import {
+ GuestBookApiActions,
+ GuestBookPageActions,
+} from './guest-book.actions';
+import { GuestBookEffects } from './guest-book.effects';
+import { selectGuestBookEntriesLoaded } from './guest-book.selectors';
+
+describe('GuestBookEffects', () => {
+ let actions$ = new Observable();
+ let store: MockStore;
+ let mockGuestBookService: jasmine.SpyObj;
+ let testScheduler: TestScheduler;
+ let effects: GuestBookEffects;
+
+ beforeEach(() => {
+ mockGuestBookService = jasmine.createSpyObj(['getEntries', 'save']);
+ testScheduler = new TestScheduler((actual, expected) => {
+ expect(actual).toEqual(expected);
+ });
+
+ TestBed.configureTestingModule({
+ providers: [
+ provideMockActions(() => actions$),
+ provideMockStore({
+ selectors: [
+ {
+ selector: selectGuestBookEntriesLoaded,
+ value: false,
+ },
+ ],
+ }),
+ GuestBookEffects,
+ {
+ provide: GuestBookService,
+ useValue: mockGuestBookService,
+ },
+ ],
+ });
+
+ actions$ = TestBed.inject(Actions);
+ effects = TestBed.inject(GuestBookEffects);
+ store = TestBed.inject(MockStore);
+ });
+
+ describe('loadEntries$', () => {
+ it('should return loaded success on pageOpened', () => {
+ const entries: GuestBookEntry[] = [
+ {
+ id: 1,
+ author: {
+ name: 'Author 1',
+ email: 'author1@example.com',
+ },
+ message: 'Message 1',
+ },
+ {
+ id: 2,
+ author: {
+ name: 'Author 2',
+ email: 'author2@example.com',
+ },
+ message: 'Message 2',
+ },
+ ];
+
+ testScheduler.run(({ cold, hot, expectObservable }) => {
+ actions$ = hot('-a', {
+ a: GuestBookPageActions.pageOpened(),
+ });
+ mockGuestBookService.getEntries.and.returnValue(
+ cold('--b|', { b: entries }),
+ );
+
+ expectObservable(effects.loadEntries$).toBe('---c', {
+ c: GuestBookApiActions.entriesLoadedSuccess({ entries }),
+ });
+ });
+ });
+
+ it('should return loaded fail when service fails', () => {
+ const error = 'test error';
+ testScheduler.run(({ hot, cold, expectObservable }) => {
+ actions$ = hot('-a', { a: GuestBookPageActions.pageOpened() });
+ mockGuestBookService.getEntries.and.returnValue(
+ cold('--#', undefined, error),
+ );
+
+ expectObservable(effects.loadEntries$).toBe('---c', {
+ c: GuestBookApiActions.entriesLoadedFail({
+ message: error,
+ }),
+ });
+ });
+ });
+
+ it('should not load entries when they were already loaded', () => {
+ store.overrideSelector(selectGuestBookEntriesLoaded, true);
+ testScheduler.run(({ hot, expectObservable }) => {
+ actions$ = hot('-a--', {
+ a: GuestBookPageActions.pageOpened(),
+ });
+
+ expectObservable(effects.loadEntries$).toBe('----');
+ });
+ });
+ });
+
+ describe('saveEntry$', () => {
+ it('should return saved success on entry saved', () => {
+ const entry: GuestBookEntry = {
+ author: {
+ name: 'Author 1',
+ email: 'author1@example.com',
+ },
+ message: 'Message 1',
+ };
+
+ const savedEntry: GuestBookEntry = {
+ ...entry,
+ id: 1,
+ };
+
+ testScheduler.run(({ cold, hot, expectObservable }) => {
+ actions$ = hot('-a', {
+ a: GuestBookPageActions.entrySaved({ entry }),
+ });
+ mockGuestBookService.save.and.returnValue(
+ cold('--b|', { b: savedEntry }),
+ );
+
+ expectObservable(effects.saveEntry$).toBe('---c', {
+ c: GuestBookApiActions.entrySavedSuccess({ entry: savedEntry }),
+ });
+ });
+ });
+
+ it('should return saved fail when service fails', () => {
+ const entry: GuestBookEntry = {
+ author: {
+ name: 'Author 1',
+ email: 'author1@example.com',
+ },
+ message: 'Message 1',
+ };
+
+ const error = 'test error';
+
+ testScheduler.run(({ hot, cold, expectObservable }) => {
+ actions$ = hot('-a', { a: GuestBookPageActions.entrySaved({ entry }) });
+ mockGuestBookService.save.and.returnValue(
+ cold('--#', undefined, error),
+ );
+
+ expectObservable(effects.saveEntry$).toBe('---c', {
+ c: GuestBookApiActions.entrySavedFail({
+ message: error,
+ }),
+ });
+ });
+ });
+ });
+});
diff --git a/src/app/guest-book/state/guest-book.effects.ts b/src/app/guest-book/state/guest-book.effects.ts
new file mode 100644
index 0000000..362c939
--- /dev/null
+++ b/src/app/guest-book/state/guest-book.effects.ts
@@ -0,0 +1,54 @@
+import { Injectable } from '@angular/core';
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { concatLatestFrom } from '@ngrx/operators';
+import { Store } from '@ngrx/store';
+import { catchError, exhaustMap, filter, map, of, switchMap } from 'rxjs';
+import { GuestBookService } from '../guest-book.service';
+import {
+ GuestBookApiActions,
+ GuestBookPageActions,
+} from './guest-book.actions';
+import { selectGuestBookEntriesLoaded } from './guest-book.selectors';
+
+@Injectable()
+export class GuestBookEffects {
+ loadEntries$ = createEffect(() => {
+ return this.actions$.pipe(
+ ofType(GuestBookPageActions.pageOpened),
+ concatLatestFrom(() => this.store.select(selectGuestBookEntriesLoaded)),
+ filter(([, loaded]) => !loaded),
+ exhaustMap(() =>
+ this.guestBookService.getEntries().pipe(
+ map((entries) =>
+ GuestBookApiActions.entriesLoadedSuccess({ entries }),
+ ),
+ catchError((error) =>
+ of(GuestBookApiActions.entriesLoadedFail({ message: error })),
+ ),
+ ),
+ ),
+ );
+ });
+
+ saveEntry$ = createEffect(() => {
+ return this.actions$.pipe(
+ ofType(GuestBookPageActions.entrySaved),
+ switchMap(({ entry }) =>
+ this.guestBookService.save(entry).pipe(
+ map((savedEntry) =>
+ GuestBookApiActions.entrySavedSuccess({ entry: savedEntry }),
+ ),
+ catchError((error) =>
+ of(GuestBookApiActions.entrySavedFail({ message: error })),
+ ),
+ ),
+ ),
+ );
+ });
+
+ constructor(
+ private actions$: Actions,
+ private store: Store,
+ private guestBookService: GuestBookService,
+ ) {}
+}
diff --git a/src/app/guest-book/state/guest-book.reducer.spec.ts b/src/app/guest-book/state/guest-book.reducer.spec.ts
new file mode 100644
index 0000000..adffe6a
--- /dev/null
+++ b/src/app/guest-book/state/guest-book.reducer.spec.ts
@@ -0,0 +1,179 @@
+import { GuestBookEntry } from '../guest-book.model';
+import {
+ GuestBookApiActions,
+ GuestBookPageActions,
+} from './guest-book.actions';
+import { guestBookFeature, GuestBookState } from './guest-book.reducer';
+
+describe('Guest Book Reducer', () => {
+ const DEFAULT_STATE: GuestBookState = {
+ ids: [],
+ entities: {},
+ loading: false,
+ loaded: false,
+ saving: false,
+ loadErrorMessage: '',
+ saveErrorMessage: '',
+ };
+
+ const DEFAULT_ENTRY: GuestBookEntry = {
+ id: 1,
+ author: {
+ name: 'Name 1',
+ email: 'author1@example.com',
+ },
+ message: 'Message 1',
+ };
+
+ describe('GuestBookPageActions.pageOpened action', () => {
+ it('should set loading to true when not loaded', () => {
+ const action = GuestBookPageActions.pageOpened();
+ const initialState: GuestBookState = {
+ ...DEFAULT_STATE,
+ };
+ const expectedState: GuestBookState = {
+ ...DEFAULT_STATE,
+ loading: true,
+ };
+
+ const result = guestBookFeature.reducer(initialState, action);
+
+ expect(result).toEqual(expectedState);
+ });
+
+ it('should set loading to false when loaded', () => {
+ const action = GuestBookPageActions.pageOpened();
+ const initialState: GuestBookState = {
+ ...DEFAULT_STATE,
+ loaded: true,
+ };
+ const expectedState: GuestBookState = {
+ ...initialState,
+ loading: false,
+ };
+
+ const result = guestBookFeature.reducer(initialState, action);
+
+ expect(result).toEqual(expectedState);
+ });
+
+ it('should reset the error message', () => {
+ const action = GuestBookPageActions.pageOpened();
+ const initialState: GuestBookState = {
+ ...DEFAULT_STATE,
+ loadErrorMessage: 'Error Message',
+ loaded: true,
+ };
+
+ const result = guestBookFeature.reducer(initialState, action);
+
+ expect(result.loadErrorMessage).toEqual('');
+ });
+ });
+
+ describe('GuestBookPageActions.entrySaved', () => {
+ it('should set saving to true', () => {
+ const action = GuestBookPageActions.entrySaved({ entry: DEFAULT_ENTRY });
+ const initialState: GuestBookState = {
+ ...DEFAULT_STATE,
+ saving: false,
+ };
+ const expectedState: GuestBookState = {
+ ...DEFAULT_STATE,
+ saving: true,
+ };
+
+ const result = guestBookFeature.reducer(initialState, action);
+
+ expect(result).toEqual(expectedState);
+ });
+ });
+
+ describe('GuestBookApiActions.entriesLoadedSuccess', () => {
+ it('should set entities, loading to false and loaded to true', () => {
+ const action = GuestBookApiActions.entriesLoadedSuccess({
+ entries: [DEFAULT_ENTRY],
+ });
+ const initialState: GuestBookState = {
+ ...DEFAULT_STATE,
+ loading: true,
+ loaded: false,
+ };
+ const expectedState: GuestBookState = {
+ ...DEFAULT_STATE,
+ ids: [1],
+ entities: {
+ 1: DEFAULT_ENTRY,
+ },
+ loading: false,
+ loaded: true,
+ };
+
+ const result = guestBookFeature.reducer(initialState, action);
+
+ expect(result).toEqual(expectedState);
+ });
+ });
+
+ describe('GuestBookApiActions.entriesLoadedFail', () => {
+ it('should set error message and loading to false', () => {
+ const error = 'Error Message';
+ const action = GuestBookApiActions.entriesLoadedFail({ message: error });
+ const initialState: GuestBookState = {
+ ...DEFAULT_STATE,
+ loading: true,
+ };
+ const expectedState: GuestBookState = {
+ ...DEFAULT_STATE,
+ loading: false,
+ loadErrorMessage: error,
+ };
+
+ const result = guestBookFeature.reducer(initialState, action);
+
+ expect(result).toEqual(expectedState);
+ });
+ });
+
+ describe('GuestBookApiActions.entrySavedSuccess', () => {
+ it('should add entry and set saving to false', () => {
+ const action = GuestBookApiActions.entrySavedSuccess({
+ entry: DEFAULT_ENTRY,
+ });
+ const initialState: GuestBookState = {
+ ...DEFAULT_STATE,
+ saving: true,
+ };
+ const expectedState: GuestBookState = {
+ ...DEFAULT_STATE,
+ ids: [1],
+ entities: { 1: DEFAULT_ENTRY },
+ saving: false,
+ };
+
+ const result = guestBookFeature.reducer(initialState, action);
+
+ expect(result).toEqual(expectedState);
+ });
+ });
+
+ describe('GuestBookApiActions.entrySavedFail', () => {
+ it('should set error message and loading to false', () => {
+ const error = 'Error Message';
+ const action = GuestBookApiActions.entrySavedFail({ message: error });
+ const initialState: GuestBookState = {
+ ...DEFAULT_STATE,
+ loading: true,
+ };
+ const expectedState: GuestBookState = {
+ ...DEFAULT_STATE,
+ loading: false,
+ saveErrorMessage: error,
+ };
+
+ const result = guestBookFeature.reducer(initialState, action);
+
+ expect(result).toEqual(expectedState);
+ });
+ });
+});
diff --git a/src/app/guest-book/state/guest-book.reducer.ts b/src/app/guest-book/state/guest-book.reducer.ts
new file mode 100644
index 0000000..d736a68
--- /dev/null
+++ b/src/app/guest-book/state/guest-book.reducer.ts
@@ -0,0 +1,85 @@
+import { createEntityAdapter, EntityState } from '@ngrx/entity';
+import { createFeature, createReducer, on } from '@ngrx/store';
+import { GuestBookEntry } from '../guest-book.model';
+import {
+ GuestBookApiActions,
+ GuestBookPageActions,
+} from './guest-book.actions';
+
+export interface GuestBookState extends EntityState {
+ loading: boolean;
+ loaded: boolean;
+ loadErrorMessage: string;
+ saving: boolean;
+ saveErrorMessage: string;
+}
+
+const adapter = createEntityAdapter({});
+
+const initialState: GuestBookState = adapter.getInitialState({
+ loading: false,
+ loaded: false,
+ loadErrorMessage: '',
+ saving: false,
+ saveErrorMessage: '',
+});
+
+export const guestBookFeature = createFeature({
+ name: 'guestBook',
+ reducer: createReducer(
+ initialState,
+ on(
+ GuestBookPageActions.pageOpened,
+ (state): GuestBookState => ({
+ ...state,
+ loading: !state.loaded,
+ loadErrorMessage: '',
+ }),
+ ),
+ on(
+ GuestBookPageActions.entrySaved,
+ (state): GuestBookState => ({
+ ...state,
+ saving: true,
+ saveErrorMessage: '',
+ }),
+ ),
+ on(
+ GuestBookApiActions.entriesLoadedSuccess,
+ (state, { entries }): GuestBookState =>
+ adapter.addMany(entries, {
+ ...state,
+ loading: false,
+ loaded: true,
+ }),
+ ),
+ on(
+ GuestBookApiActions.entriesLoadedFail,
+ (state, { message }): GuestBookState => ({
+ ...state,
+ loading: false,
+ loadErrorMessage: message,
+ }),
+ ),
+ on(
+ GuestBookApiActions.entrySavedSuccess,
+ (state, { entry }): GuestBookState =>
+ adapter.addOne(entry, {
+ ...state,
+ saving: false,
+ }),
+ ),
+ on(
+ GuestBookApiActions.entrySavedFail,
+ (state, { message }): GuestBookState => ({
+ ...state,
+ loading: false,
+ saveErrorMessage: message,
+ }),
+ ),
+ ),
+});
+
+const { selectAll } = adapter.getSelectors();
+
+export const selectGuestBookEntries = selectAll;
diff --git a/src/app/guest-book/state/guest-book.selectors.spec.ts b/src/app/guest-book/state/guest-book.selectors.spec.ts
new file mode 100644
index 0000000..d6ff8c2
--- /dev/null
+++ b/src/app/guest-book/state/guest-book.selectors.spec.ts
@@ -0,0 +1,106 @@
+import { GuestBookState } from './guest-book.reducer';
+import {
+ selectGuestBookEntries,
+ selectGuestBookEntriesErrorMessage,
+ selectGuestBookEntriesLoaded,
+ selectGuestBookEntriesLoading,
+ selectGuestBookEntrySaving,
+} from './guest-book.selectors';
+
+describe('Guest Book Selectors', () => {
+ const entries = [
+ {
+ id: 1,
+ author: {
+ name: 'Name 1',
+ email: 'Email 1',
+ },
+ message: 'Message 1',
+ },
+ {
+ id: 2,
+ author: {
+ name: 'Name 2',
+ email: 'Email 2',
+ },
+ message: 'Message 2',
+ },
+ ];
+
+ const initialState: GuestBookState = {
+ entities: {
+ 1: entries[0],
+ 2: entries[1],
+ },
+ ids: [1, 2],
+ loading: false,
+ loaded: false,
+ saving: false,
+ loadErrorMessage: 'Error Message',
+ saveErrorMessage: '',
+ };
+
+ it('should select entries', () => {
+ const result = selectGuestBookEntries.projector(initialState);
+ expect(result).toEqual(entries);
+ });
+
+ it('should select entries loading', () => {
+ const state1 = {
+ ...initialState,
+ loading: true,
+ };
+
+ const state2 = {
+ ...initialState,
+ loading: false,
+ };
+
+ const result1 = selectGuestBookEntriesLoading.projector(state1);
+ const result2 = selectGuestBookEntriesLoading.projector(state2);
+
+ expect(result1).toBeTrue();
+ expect(result2).toBeFalse();
+ });
+
+ it('should select entries loaded', () => {
+ const state1 = {
+ ...initialState,
+ loaded: true,
+ };
+
+ const state2 = {
+ ...initialState,
+ loaded: false,
+ };
+
+ const result1 = selectGuestBookEntriesLoaded.projector(state1);
+ const result2 = selectGuestBookEntriesLoaded.projector(state2);
+
+ expect(result1).toBeTrue();
+ expect(result2).toBeFalse();
+ });
+
+ it('should select entries error message', () => {
+ const result = selectGuestBookEntriesErrorMessage.projector(initialState);
+ expect(result).toEqual('Error Message');
+ });
+
+ it('should select entry saving', () => {
+ const state1 = {
+ ...initialState,
+ saving: true,
+ };
+
+ const state2 = {
+ ...initialState,
+ saving: false,
+ };
+
+ const result1 = selectGuestBookEntrySaving.projector(state1);
+ const result2 = selectGuestBookEntrySaving.projector(state2);
+
+ expect(result1).toBeTrue();
+ expect(result2).toBeFalse();
+ });
+});
diff --git a/src/app/guest-book/state/guest-book.selectors.ts b/src/app/guest-book/state/guest-book.selectors.ts
new file mode 100644
index 0000000..f563831
--- /dev/null
+++ b/src/app/guest-book/state/guest-book.selectors.ts
@@ -0,0 +1,31 @@
+import { createFeatureSelector, createSelector } from '@ngrx/store';
+import * as reducer from './guest-book.reducer';
+
+const selectGuestBookState = createFeatureSelector(
+ reducer.guestBookFeature.name,
+);
+
+export const selectGuestBookEntries = createSelector(
+ selectGuestBookState,
+ reducer.selectGuestBookEntries,
+);
+
+export const selectGuestBookEntriesLoading = createSelector(
+ selectGuestBookState,
+ (state) => state.loading,
+);
+
+export const selectGuestBookEntriesLoaded = createSelector(
+ selectGuestBookState,
+ (state) => state.loaded,
+);
+
+export const selectGuestBookEntriesErrorMessage = createSelector(
+ selectGuestBookState,
+ (state) => state.loadErrorMessage,
+);
+
+export const selectGuestBookEntrySaving = createSelector(
+ selectGuestBookState,
+ (state) => state.saving,
+);
diff --git a/src/app/page-not-found/page-not-found.component.html b/src/app/page-not-found/page-not-found.component.html
new file mode 100644
index 0000000..a8ec4b4
--- /dev/null
+++ b/src/app/page-not-found/page-not-found.component.html
@@ -0,0 +1 @@
+Page not found.
diff --git a/src/app/page-not-found/page-not-found.component.scss b/src/app/page-not-found/page-not-found.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/page-not-found/page-not-found.component.ts b/src/app/page-not-found/page-not-found.component.ts
new file mode 100644
index 0000000..fd8b12b
--- /dev/null
+++ b/src/app/page-not-found/page-not-found.component.ts
@@ -0,0 +1,10 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+
+@Component({
+ selector: 'app-page-not-found',
+ imports: [],
+ templateUrl: './page-not-found.component.html',
+ styleUrl: './page-not-found.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class PageNotFoundComponent {}
diff --git a/src/app/posts/post-comments/post-comments.component.html b/src/app/posts/post-comments/post-comments.component.html
new file mode 100644
index 0000000..d0c2162
--- /dev/null
+++ b/src/app/posts/post-comments/post-comments.component.html
@@ -0,0 +1,16 @@
+@if (comments().length > 0) {
+
+
+
+ @for (comment of comments(); track comment.id) {
+
+ comment
+ {{ comment.name }}
+ {{ comment.email }}
+ {{ comment.body }}
+
+ }
+
+
+
+}
diff --git a/src/app/posts/post-comments/post-comments.component.scss b/src/app/posts/post-comments/post-comments.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/posts/post-comments/post-comments.component.ts b/src/app/posts/post-comments/post-comments.component.ts
new file mode 100644
index 0000000..ae5de46
--- /dev/null
+++ b/src/app/posts/post-comments/post-comments.component.ts
@@ -0,0 +1,31 @@
+import { ChangeDetectionStrategy, Component, input } from '@angular/core';
+import { MatCard, MatCardContent } from '@angular/material/card';
+import { MatIcon } from '@angular/material/icon';
+import {
+ MatList,
+ MatListItem,
+ MatListItemIcon,
+ MatListItemLine,
+ MatListItemTitle,
+} from '@angular/material/list';
+import { PostComment } from '../posts.model';
+
+@Component({
+ selector: 'app-post-comments',
+ imports: [
+ MatList,
+ MatListItem,
+ MatListItemTitle,
+ MatListItemLine,
+ MatIcon,
+ MatListItemIcon,
+ MatCard,
+ MatCardContent,
+ ],
+ templateUrl: './post-comments.component.html',
+ styleUrl: './post-comments.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class PostCommentsComponent {
+ comments = input([]);
+}
diff --git a/src/app/posts/post-details/post-details.component.html b/src/app/posts/post-details/post-details.component.html
new file mode 100644
index 0000000..62d0a62
--- /dev/null
+++ b/src/app/posts/post-details/post-details.component.html
@@ -0,0 +1,21 @@
+
+
+
+ {{ post().title }}
+
+
+ {{ user()?.name ?? "..." }}
+
+
+
+ {{ post().body }}
+
+ @if (showActions()) {
+
+
+ comment
+ Comments
+
+ }
+
diff --git a/src/app/posts/post-details/post-details.component.scss b/src/app/posts/post-details/post-details.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/posts/post-details/post-details.component.ts b/src/app/posts/post-details/post-details.component.ts
new file mode 100644
index 0000000..d000bf9
--- /dev/null
+++ b/src/app/posts/post-details/post-details.component.ts
@@ -0,0 +1,20 @@
+import { ChangeDetectionStrategy, Component, input } from '@angular/core';
+import { MatButtonModule } from '@angular/material/button';
+import { MatCardModule } from '@angular/material/card';
+import { MatIcon } from '@angular/material/icon';
+import { RouterLink } from '@angular/router';
+import { User } from 'src/app/users/user.model';
+import { Post } from '../posts.model';
+
+@Component({
+ selector: 'app-post-details',
+ imports: [MatCardModule, MatButtonModule, RouterLink, MatIcon],
+ templateUrl: './post-details.component.html',
+ styleUrl: './post-details.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class PostDetailsComponent {
+ post = input.required();
+ user = input();
+ showActions = input(true);
+}
diff --git a/src/app/posts/post-page/post-page.component.html b/src/app/posts/post-page/post-page.component.html
new file mode 100644
index 0000000..8b31859
--- /dev/null
+++ b/src/app/posts/post-page/post-page.component.html
@@ -0,0 +1,22 @@
+
+
+ arrow_back
+ Posts
+
+
+ @if (postsLoading()) {
+
Loading...
+ } @else if (postsErrorMessage()) {
+
Error {{ postsErrorMessage() }}
+ } @else if (post()) {
+
+
+ @if (commentsErrorMessage()) {
+
Error {{ commentsErrorMessage() }}
+ } @else {
+
+ }
+ } @else {
+
Post not found.
+ }
+
diff --git a/src/app/posts/post-page/post-page.component.scss b/src/app/posts/post-page/post-page.component.scss
new file mode 100644
index 0000000..6152f75
--- /dev/null
+++ b/src/app/posts/post-page/post-page.component.scss
@@ -0,0 +1,9 @@
+.post-page {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+
+ .posts-link {
+ max-width: fit-content;
+ }
+}
diff --git a/src/app/posts/post-page/post-page.component.spec.ts b/src/app/posts/post-page/post-page.component.spec.ts
new file mode 100644
index 0000000..cd1eeb8
--- /dev/null
+++ b/src/app/posts/post-page/post-page.component.spec.ts
@@ -0,0 +1,134 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ActivatedRoute } from '@angular/router';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { of, throwError } from 'rxjs';
+import { selectUsersEntities } from 'src/app/users/state/users.selectors';
+import { User } from 'src/app/users/user.model';
+import { Post, PostComment } from '../posts.model';
+import { PostsService } from '../posts.service';
+import { PostPageActions } from '../state/posts.actions';
+import {
+ selectPostById,
+ selectPostsErrorMessage,
+ selectPostsLoading,
+} from '../state/posts.selectors';
+import { PostPageComponent } from './post-page.component';
+
+describe('PostPageComponent', () => {
+ let mockPostsService: jasmine.SpyObj;
+ let store: MockStore;
+ let component: PostPageComponent;
+ let fixture: ComponentFixture;
+
+ const user: User = {
+ id: 1,
+ name: 'Test User',
+ };
+
+ beforeEach(async () => {
+ mockPostsService = jasmine.createSpyObj(['getComments']);
+
+ TestBed.configureTestingModule({
+ imports: [PostPageComponent],
+ providers: [
+ {
+ provide: ActivatedRoute,
+ useValue: {
+ params: of({ id: 1 }),
+ },
+ },
+ provideMockStore({
+ selectors: [
+ {
+ selector: selectPostById,
+ value: {
+ id: 1,
+ userId: user.id,
+ title: 'Post Title',
+ body: 'Post Body',
+ } as Post,
+ },
+ {
+ selector: selectPostsLoading,
+ value: false,
+ },
+ {
+ selector: selectPostsErrorMessage,
+ value: '',
+ },
+ {
+ selector: selectUsersEntities,
+ value: {
+ [user.id]: user,
+ },
+ },
+ ],
+ }),
+ {
+ provide: PostsService,
+ useValue: mockPostsService,
+ },
+ ],
+ });
+
+ fixture = TestBed.createComponent(PostPageComponent);
+ component = fixture.componentInstance;
+ store = TestBed.inject(MockStore);
+ });
+
+ it('should dispatch page opened action on init', () => {
+ const dispatchSpy = spyOn(store, 'dispatch');
+ component.ngOnInit();
+
+ expect(dispatchSpy).toHaveBeenCalledOnceWith(PostPageActions.pageOpened());
+ });
+
+ it('should return user by current post user id', () => {
+ expect(component.user()).toBe(user);
+ });
+
+ it('should return undefined user if post is undefined', () => {
+ store.overrideSelector(selectPostById, undefined);
+ expect(component.user()).toBeUndefined();
+ });
+
+ it('should return empty comments when post is undefined', () => {
+ store.overrideSelector(selectPostById, undefined);
+ fixture.detectChanges();
+ expect(component.postComments()).toEqual([]);
+ });
+
+ it('should return post comments by id', () => {
+ const comments: PostComment[] = [
+ {
+ id: 1,
+ postId: 1,
+ name: 'Name 1',
+ email: 'email1@example.com',
+ body: 'Body 1',
+ },
+ {
+ id: 2,
+ postId: 1,
+ name: 'Name 2',
+ email: 'email2@example.com',
+ body: 'Body 2',
+ },
+ ];
+
+ mockPostsService.getComments.and.returnValue(of(comments));
+ fixture.detectChanges();
+
+ expect(component.postComments()).toEqual(comments);
+ });
+
+ it('should return empty array and set error message on posts comments error', () => {
+ const error = 'test error';
+ mockPostsService.getComments.and.returnValue(throwError(() => error));
+
+ fixture.detectChanges();
+
+ expect(component.postComments()).toEqual([]);
+ expect(component.commentsErrorMessage()).toEqual(error);
+ });
+});
diff --git a/src/app/posts/post-page/post-page.component.ts b/src/app/posts/post-page/post-page.component.ts
new file mode 100644
index 0000000..634e165
--- /dev/null
+++ b/src/app/posts/post-page/post-page.component.ts
@@ -0,0 +1,64 @@
+import { Component, computed, inject, OnInit, signal } from '@angular/core';
+import { toObservable, toSignal } from '@angular/core/rxjs-interop';
+import { MatButtonModule } from '@angular/material/button';
+import { MatIcon } from '@angular/material/icon';
+import { RouterLink } from '@angular/router';
+import { Store } from '@ngrx/store';
+import { catchError, filter, map, of, switchMap } from 'rxjs';
+import { selectUsersEntities } from 'src/app/users/state/users.selectors';
+import { PostCommentsComponent } from '../post-comments/post-comments.component';
+import { PostDetailsComponent } from '../post-details/post-details.component';
+import { PostsService } from '../posts.service';
+import { PostPageActions } from '../state/posts.actions';
+import {
+ selectPostById,
+ selectPostsErrorMessage,
+ selectPostsLoading,
+} from '../state/posts.selectors';
+
+@Component({
+ selector: 'app-post-page',
+ imports: [
+ PostDetailsComponent,
+ PostCommentsComponent,
+ RouterLink,
+ MatButtonModule,
+ MatIcon,
+ ],
+ templateUrl: './post-page.component.html',
+ styleUrl: './post-page.component.scss',
+})
+export class PostPageComponent implements OnInit {
+ private store = inject(Store);
+ private postsService = inject(PostsService);
+
+ post = this.store.selectSignal(selectPostById);
+ postsLoading = this.store.selectSignal(selectPostsLoading);
+ postsErrorMessage = this.store.selectSignal(selectPostsErrorMessage);
+ commentsErrorMessage = signal('');
+
+ private users = this.store.selectSignal(selectUsersEntities);
+ user = computed(() => {
+ if (!this.post()) {
+ return undefined;
+ }
+
+ return this.users()[this.post()!.userId];
+ });
+
+ private postComments$ = toObservable(this.post).pipe(
+ filter((p) => Boolean(p)),
+ map((p) => p!.id),
+ switchMap((id) => this.postsService.getComments(id)),
+ catchError((error) => {
+ this.commentsErrorMessage.set(error);
+ return of([]);
+ }),
+ );
+
+ postComments = toSignal(this.postComments$, { initialValue: [] });
+
+ ngOnInit(): void {
+ this.store.dispatch(PostPageActions.pageOpened());
+ }
+}
diff --git a/src/app/posts/posts-list/posts-list.component.html b/src/app/posts/posts-list/posts-list.component.html
new file mode 100644
index 0000000..043211b
--- /dev/null
+++ b/src/app/posts/posts-list/posts-list.component.html
@@ -0,0 +1,5 @@
+
+ @for (post of posts(); track post.id) {
+
+ }
+
diff --git a/src/app/posts/posts-list/posts-list.component.scss b/src/app/posts/posts-list/posts-list.component.scss
new file mode 100644
index 0000000..a38905f
--- /dev/null
+++ b/src/app/posts/posts-list/posts-list.component.scss
@@ -0,0 +1,5 @@
+.posts {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
diff --git a/src/app/posts/posts-list/posts-list.component.ts b/src/app/posts/posts-list/posts-list.component.ts
new file mode 100644
index 0000000..690a9e7
--- /dev/null
+++ b/src/app/posts/posts-list/posts-list.component.ts
@@ -0,0 +1,16 @@
+import { ChangeDetectionStrategy, Component, input } from '@angular/core';
+import { User } from 'src/app/users/user.model';
+import { PostDetailsComponent } from '../post-details/post-details.component';
+import { Post } from '../posts.model';
+
+@Component({
+ selector: 'app-posts-list',
+ imports: [PostDetailsComponent],
+ templateUrl: './posts-list.component.html',
+ styleUrl: './posts-list.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class PostsListComponent {
+ posts = input([]);
+ users = input>({});
+}
diff --git a/src/app/posts/posts-page/posts-page.component.html b/src/app/posts/posts-page/posts-page.component.html
new file mode 100644
index 0000000..f4a3ec8
--- /dev/null
+++ b/src/app/posts/posts-page/posts-page.component.html
@@ -0,0 +1,7 @@
+@if (loading()) {
+ Loading...
+} @else if (errorMessage()) {
+ Error {{ errorMessage() }}
+} @else {
+
+}
diff --git a/src/app/posts/posts-page/posts-page.component.scss b/src/app/posts/posts-page/posts-page.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/posts/posts-page/posts-page.component.spec.ts b/src/app/posts/posts-page/posts-page.component.spec.ts
new file mode 100644
index 0000000..eabba1d
--- /dev/null
+++ b/src/app/posts/posts-page/posts-page.component.spec.ts
@@ -0,0 +1,28 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { PostsPageActions } from '../state/posts.actions';
+import { PostsPageComponent } from './posts-page.component';
+
+describe('PostsPageComponent', () => {
+ let store: MockStore;
+ let component: PostsPageComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ TestBed.configureTestingModule({
+ imports: [PostsPageComponent],
+ providers: [provideMockStore()],
+ });
+
+ fixture = TestBed.createComponent(PostsPageComponent);
+ component = fixture.componentInstance;
+ store = TestBed.inject(MockStore);
+ });
+
+ it('should dispatch page opened action on init', () => {
+ const dispatchSpy = spyOn(store, 'dispatch');
+ component.ngOnInit();
+
+ expect(dispatchSpy).toHaveBeenCalledOnceWith(PostsPageActions.pageOpened());
+ });
+});
diff --git a/src/app/posts/posts-page/posts-page.component.ts b/src/app/posts/posts-page/posts-page.component.ts
new file mode 100644
index 0000000..4103b7b
--- /dev/null
+++ b/src/app/posts/posts-page/posts-page.component.ts
@@ -0,0 +1,29 @@
+import { Component, inject, OnInit } from '@angular/core';
+import { Store } from '@ngrx/store';
+import { selectUsersEntities } from 'src/app/users/state/users.selectors';
+import { PostsListComponent } from '../posts-list/posts-list.component';
+import { PostsPageActions } from '../state/posts.actions';
+import {
+ selectPosts,
+ selectPostsErrorMessage,
+ selectPostsLoading,
+} from '../state/posts.selectors';
+
+@Component({
+ selector: 'app-posts-page',
+ imports: [PostsListComponent],
+ templateUrl: './posts-page.component.html',
+ styleUrl: './posts-page.component.scss',
+})
+export class PostsPageComponent implements OnInit {
+ private store = inject(Store);
+
+ posts = this.store.selectSignal(selectPosts);
+ users = this.store.selectSignal(selectUsersEntities);
+ loading = this.store.selectSignal(selectPostsLoading);
+ errorMessage = this.store.selectSignal(selectPostsErrorMessage);
+
+ ngOnInit(): void {
+ this.store.dispatch(PostsPageActions.pageOpened());
+ }
+}
diff --git a/src/app/posts/posts.model.ts b/src/app/posts/posts.model.ts
new file mode 100644
index 0000000..e789b55
--- /dev/null
+++ b/src/app/posts/posts.model.ts
@@ -0,0 +1,14 @@
+export interface Post {
+ id: number;
+ userId: number;
+ title: string;
+ body: string;
+}
+
+export interface PostComment {
+ id: number;
+ postId: number;
+ name: string;
+ email: string;
+ body: string;
+}
diff --git a/src/app/posts/posts.routes.ts b/src/app/posts/posts.routes.ts
new file mode 100644
index 0000000..49a75b8
--- /dev/null
+++ b/src/app/posts/posts.routes.ts
@@ -0,0 +1,14 @@
+import { Routes } from '@angular/router';
+import { PostPageComponent } from './post-page/post-page.component';
+import { PostsPageComponent } from './posts-page/posts-page.component';
+
+export const routes: Routes = [
+ {
+ path: '',
+ component: PostsPageComponent,
+ },
+ {
+ path: ':id',
+ component: PostPageComponent,
+ },
+];
diff --git a/src/app/posts/posts.service.spec.ts b/src/app/posts/posts.service.spec.ts
new file mode 100644
index 0000000..f3138fc
--- /dev/null
+++ b/src/app/posts/posts.service.spec.ts
@@ -0,0 +1,107 @@
+import { TestBed } from '@angular/core/testing';
+
+import { provideHttpClient } from '@angular/common/http';
+import {
+ HttpTestingController,
+ provideHttpClientTesting,
+} from '@angular/common/http/testing';
+import { firstValueFrom } from 'rxjs';
+import { Post, PostComment } from './posts.model';
+import { PostsService } from './posts.service';
+
+describe('PostsService', () => {
+ let http: HttpTestingController;
+ let service: PostsService;
+
+ let DEFAULT_POSTS: Post[];
+ let DEFAULT_COMMENTS: PostComment[];
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [
+ PostsService,
+ provideHttpClient(),
+ provideHttpClientTesting(),
+ ],
+ });
+
+ http = TestBed.inject(HttpTestingController);
+ service = TestBed.inject(PostsService);
+
+ DEFAULT_POSTS = [
+ {
+ id: 1,
+ userId: 1,
+ body: 'Body 1',
+ title: 'Title 1',
+ },
+ {
+ id: 2,
+ userId: 2,
+ body: 'Body 2',
+ title: 'Title 2',
+ },
+ {
+ id: 3,
+ userId: 3,
+ body: 'Body 3',
+ title: 'Title 3',
+ },
+ ];
+
+ DEFAULT_COMMENTS = [
+ {
+ id: 1,
+ postId: 1,
+ name: 'Name 1',
+ email: 'email1@example.com',
+ body: 'Comment 1',
+ },
+ {
+ id: 2,
+ postId: 1,
+ name: 'Name 2',
+ email: 'email2@example.com',
+ body: 'Comment 2',
+ },
+ {
+ id: 3,
+ postId: 1,
+ name: 'Name 3',
+ email: 'email3@example.com',
+ body: 'Comment 3',
+ },
+ ];
+ });
+
+ afterEach(() => {
+ TestBed.inject(HttpTestingController).verify();
+ });
+
+ it('should return posts array from getPosts', async () => {
+ const posts$ = service.getPosts();
+ const postsPromise = firstValueFrom(posts$);
+
+ const req = http.expectOne({
+ url: 'https://jsonplaceholder.typicode.com/posts',
+ method: 'GET',
+ });
+ req.flush(DEFAULT_POSTS);
+
+ expect(await postsPromise).toEqual(DEFAULT_POSTS);
+ });
+
+ it('should return comments array for the provided post ID from getComments', async () => {
+ const postId = 1;
+ const comments$ = service.getComments(postId);
+ const commentsPromise = firstValueFrom(comments$);
+
+ const req = http.expectOne({
+ url: 'https://jsonplaceholder.typicode.com/posts/1/comments',
+ method: 'GET',
+ });
+ req.flush(DEFAULT_COMMENTS);
+
+ expect(await commentsPromise).toEqual(DEFAULT_COMMENTS);
+ });
+});
diff --git a/src/app/posts/posts.service.ts b/src/app/posts/posts.service.ts
new file mode 100644
index 0000000..229878c
--- /dev/null
+++ b/src/app/posts/posts.service.ts
@@ -0,0 +1,27 @@
+import { HttpClient } from '@angular/common/http';
+import { inject, Injectable } from '@angular/core';
+import { catchError } from 'rxjs';
+import { handleError } from '../utils/http';
+import { Post, PostComment } from './posts.model';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class PostsService {
+ private postsUrl = 'https://jsonplaceholder.typicode.com/posts';
+ private http = inject(HttpClient);
+
+ getPosts() {
+ return this.http.get(this.postsUrl).pipe(catchError(handleError));
+ }
+
+ getComments(postId: number) {
+ return this.http
+ .get(this.getCommentsUrl(postId))
+ .pipe(catchError(handleError));
+ }
+
+ private getCommentsUrl(postId: number) {
+ return `${this.postsUrl}/${postId}/comments`;
+ }
+}
diff --git a/src/app/posts/state/posts.actions.ts b/src/app/posts/state/posts.actions.ts
new file mode 100644
index 0000000..5d8b516
--- /dev/null
+++ b/src/app/posts/state/posts.actions.ts
@@ -0,0 +1,24 @@
+import { createActionGroup, emptyProps, props } from '@ngrx/store';
+import { Post } from '../posts.model';
+
+export const PostsPageActions = createActionGroup({
+ source: 'Posts Page',
+ events: {
+ 'Page Opened': emptyProps(),
+ },
+});
+
+export const PostPageActions = createActionGroup({
+ source: 'Post Page',
+ events: {
+ 'Page Opened': emptyProps(),
+ },
+});
+
+export const PostsApiActions = createActionGroup({
+ source: 'Posts API',
+ events: {
+ 'Posts Loaded Success': props<{ posts: Post[] }>(),
+ 'Posts Loaded Fail': props<{ message: string }>(),
+ },
+});
diff --git a/src/app/posts/state/posts.effects.spec.ts b/src/app/posts/state/posts.effects.spec.ts
new file mode 100644
index 0000000..7447dbf
--- /dev/null
+++ b/src/app/posts/state/posts.effects.spec.ts
@@ -0,0 +1,168 @@
+import { TestBed } from '@angular/core/testing';
+import { Actions } from '@ngrx/effects';
+import { provideMockActions } from '@ngrx/effects/testing';
+import { Action } from '@ngrx/store';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { Observable } from 'rxjs';
+import { TestScheduler } from 'rxjs/testing';
+import { Post } from '../posts.model';
+import { PostsService } from '../posts.service';
+import {
+ PostPageActions,
+ PostsApiActions,
+ PostsPageActions,
+} from './posts.actions';
+import { PostsEffects } from './posts.effects';
+import { selectPostsLoaded } from './posts.selectors';
+
+describe('PostsEffects', () => {
+ let store: MockStore;
+ let mockPostsService: jasmine.SpyObj;
+ let testScheduler: TestScheduler;
+ let actions$ = new Observable();
+ let effects: PostsEffects;
+
+ beforeEach(() => {
+ mockPostsService = jasmine.createSpyObj(['getPosts']);
+ testScheduler = new TestScheduler((actual, expected) => {
+ expect(actual).toEqual(expected);
+ });
+
+ TestBed.configureTestingModule({
+ providers: [
+ provideMockActions(() => actions$),
+ provideMockStore({
+ selectors: [
+ {
+ selector: selectPostsLoaded,
+ value: false,
+ },
+ ],
+ }),
+ PostsEffects,
+ {
+ provide: PostsService,
+ useValue: mockPostsService,
+ },
+ ],
+ });
+
+ actions$ = TestBed.inject(Actions);
+ effects = TestBed.inject(PostsEffects);
+ store = TestBed.inject(MockStore);
+ });
+
+ describe('loadPosts$ on PostsPageActions.pageOpened', () => {
+ it('should return loaded success', () => {
+ const posts: Post[] = [
+ {
+ id: 1,
+ userId: 1,
+ title: 'Title 1',
+ body: 'Body 1',
+ },
+ {
+ id: 2,
+ userId: 2,
+ title: 'Title 2',
+ body: 'Body 2',
+ },
+ ];
+
+ testScheduler.run(({ cold, hot, expectObservable }) => {
+ actions$ = hot('-a', {
+ a: PostsPageActions.pageOpened(),
+ });
+ mockPostsService.getPosts.and.returnValue(cold('--b|', { b: posts }));
+
+ expectObservable(effects.loadPosts$).toBe('---c', {
+ c: PostsApiActions.postsLoadedSuccess({ posts }),
+ });
+ });
+ });
+
+ it('should return loaded fail when service fails', () => {
+ const error = 'test error';
+ testScheduler.run(({ hot, cold, expectObservable }) => {
+ actions$ = hot('-a', { a: PostsPageActions.pageOpened() });
+ mockPostsService.getPosts.and.returnValue(
+ cold('--#', undefined, error),
+ );
+
+ expectObservable(effects.loadPosts$).toBe('---c', {
+ c: PostsApiActions.postsLoadedFail({
+ message: error,
+ }),
+ });
+ });
+ });
+
+ it('should not load entries when they were already loaded', () => {
+ store.overrideSelector(selectPostsLoaded, true);
+ testScheduler.run(({ hot, expectObservable }) => {
+ actions$ = hot('-a--', {
+ a: PostsPageActions.pageOpened(),
+ });
+
+ expectObservable(effects.loadPosts$).toBe('----');
+ });
+ });
+ });
+
+ describe('loadPosts$ on PostPageActions.pageOpened', () => {
+ it('should return loaded success', () => {
+ const posts: Post[] = [
+ {
+ id: 1,
+ userId: 1,
+ title: 'Title 1',
+ body: 'Body 1',
+ },
+ {
+ id: 2,
+ userId: 2,
+ title: 'Title 2',
+ body: 'Body 2',
+ },
+ ];
+
+ testScheduler.run(({ cold, hot, expectObservable }) => {
+ actions$ = hot('-a', {
+ a: PostPageActions.pageOpened(),
+ });
+ mockPostsService.getPosts.and.returnValue(cold('--b|', { b: posts }));
+
+ expectObservable(effects.loadPosts$).toBe('---c', {
+ c: PostsApiActions.postsLoadedSuccess({ posts }),
+ });
+ });
+ });
+
+ it('should return loaded fail when service fails', () => {
+ const error = 'test error';
+ testScheduler.run(({ hot, cold, expectObservable }) => {
+ actions$ = hot('-a', { a: PostPageActions.pageOpened() });
+ mockPostsService.getPosts.and.returnValue(
+ cold('--#', undefined, error),
+ );
+
+ expectObservable(effects.loadPosts$).toBe('---c', {
+ c: PostsApiActions.postsLoadedFail({
+ message: error,
+ }),
+ });
+ });
+ });
+
+ it('should not load entries when they were already loaded', () => {
+ store.overrideSelector(selectPostsLoaded, true);
+ testScheduler.run(({ hot, expectObservable }) => {
+ actions$ = hot('-a--', {
+ a: PostPageActions.pageOpened(),
+ });
+
+ expectObservable(effects.loadPosts$).toBe('----');
+ });
+ });
+ });
+});
diff --git a/src/app/posts/state/posts.effects.ts b/src/app/posts/state/posts.effects.ts
new file mode 100644
index 0000000..8e3e870
--- /dev/null
+++ b/src/app/posts/state/posts.effects.ts
@@ -0,0 +1,37 @@
+import { Injectable } from '@angular/core';
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { concatLatestFrom } from '@ngrx/operators';
+import { Store } from '@ngrx/store';
+import { catchError, exhaustMap, filter, map, of } from 'rxjs';
+import { PostsService } from '../posts.service';
+import {
+ PostPageActions,
+ PostsApiActions,
+ PostsPageActions,
+} from './posts.actions';
+import { selectPostsLoaded } from './posts.selectors';
+
+@Injectable()
+export class PostsEffects {
+ loadPosts$ = createEffect(() => {
+ return this.actions$.pipe(
+ ofType(PostsPageActions.pageOpened, PostPageActions.pageOpened),
+ concatLatestFrom(() => this.store.select(selectPostsLoaded)),
+ filter(([, loaded]) => !loaded),
+ exhaustMap(() =>
+ this.postsService.getPosts().pipe(
+ map((posts) => PostsApiActions.postsLoadedSuccess({ posts })),
+ catchError((error) =>
+ of(PostsApiActions.postsLoadedFail({ message: error })),
+ ),
+ ),
+ ),
+ );
+ });
+
+ constructor(
+ private actions$: Actions,
+ private store: Store,
+ private postsService: PostsService,
+ ) {}
+}
diff --git a/src/app/posts/state/posts.reducer.spec.ts b/src/app/posts/state/posts.reducer.spec.ts
new file mode 100644
index 0000000..b261023
--- /dev/null
+++ b/src/app/posts/state/posts.reducer.spec.ts
@@ -0,0 +1,162 @@
+import { Post } from '../posts.model';
+import {
+ PostPageActions,
+ PostsApiActions,
+ PostsPageActions,
+} from './posts.actions';
+import { postsFeature, PostsState } from './posts.reducer';
+
+describe('Posts Reducer', () => {
+ const DEFAULT_STATE: PostsState = {
+ ids: [],
+ entities: {},
+ loading: false,
+ loaded: false,
+ errorMessage: '',
+ };
+
+ const DEFAULT_POST: Post = {
+ id: 1,
+ userId: 1,
+ title: 'Title 1',
+ body: 'Body 1',
+ };
+
+ describe('PostsPageActions.pageOpened action', () => {
+ it('should set loading to true when not loaded', () => {
+ const action = PostsPageActions.pageOpened();
+ const initialState: PostsState = {
+ ...DEFAULT_STATE,
+ };
+ const expectedState: PostsState = {
+ ...DEFAULT_STATE,
+ loading: true,
+ };
+
+ const result = postsFeature.reducer(initialState, action);
+
+ expect(result).toEqual(expectedState);
+ });
+
+ it('should set loading to false when loaded', () => {
+ const action = PostsPageActions.pageOpened();
+ const initialState: PostsState = {
+ ...DEFAULT_STATE,
+ loaded: true,
+ };
+ const expectedState: PostsState = {
+ ...initialState,
+ loading: false,
+ };
+
+ const result = postsFeature.reducer(initialState, action);
+
+ expect(result).toEqual(expectedState);
+ });
+
+ it('should reset the error message', () => {
+ const action = PostsPageActions.pageOpened();
+ const initialState: PostsState = {
+ ...DEFAULT_STATE,
+ errorMessage: 'Error Message',
+ loaded: true,
+ };
+
+ const result = postsFeature.reducer(initialState, action);
+
+ expect(result.errorMessage).toEqual('');
+ });
+ });
+
+ describe('PostPageActions.pageOpened action', () => {
+ it('should set loading to true when not loaded', () => {
+ const action = PostPageActions.pageOpened();
+ const initialState: PostsState = {
+ ...DEFAULT_STATE,
+ };
+ const expectedState: PostsState = {
+ ...DEFAULT_STATE,
+ loading: true,
+ };
+
+ const result = postsFeature.reducer(initialState, action);
+
+ expect(result).toEqual(expectedState);
+ });
+
+ it('should set loading to false when loaded', () => {
+ const action = PostPageActions.pageOpened();
+ const initialState: PostsState = {
+ ...DEFAULT_STATE,
+ loaded: true,
+ };
+ const expectedState: PostsState = {
+ ...initialState,
+ loading: false,
+ };
+
+ const result = postsFeature.reducer(initialState, action);
+
+ expect(result).toEqual(expectedState);
+ });
+
+ it('should reset the error message', () => {
+ const action = PostPageActions.pageOpened();
+ const initialState: PostsState = {
+ ...DEFAULT_STATE,
+ errorMessage: 'Error Message',
+ loaded: true,
+ };
+
+ const result = postsFeature.reducer(initialState, action);
+
+ expect(result.errorMessage).toEqual('');
+ });
+ });
+
+ describe('PostsApiActions.postsLoadedSuccess', () => {
+ it('should set entities, loading to false and loaded to true', () => {
+ const action = PostsApiActions.postsLoadedSuccess({
+ posts: [DEFAULT_POST],
+ });
+ const initialState: PostsState = {
+ ...DEFAULT_STATE,
+ loading: true,
+ loaded: false,
+ };
+ const expectedState: PostsState = {
+ ...DEFAULT_STATE,
+ ids: [1],
+ entities: {
+ 1: DEFAULT_POST,
+ },
+ loading: false,
+ loaded: true,
+ };
+
+ const result = postsFeature.reducer(initialState, action);
+
+ expect(result).toEqual(expectedState);
+ });
+ });
+
+ describe('PostsApiActions.postsLoadedFail', () => {
+ it('should set error message and loading to false', () => {
+ const error = 'Error Message';
+ const action = PostsApiActions.postsLoadedFail({ message: error });
+ const initialState: PostsState = {
+ ...DEFAULT_STATE,
+ loading: true,
+ };
+ const expectedState: PostsState = {
+ ...DEFAULT_STATE,
+ loading: false,
+ errorMessage: error,
+ };
+
+ const result = postsFeature.reducer(initialState, action);
+
+ expect(result).toEqual(expectedState);
+ });
+ });
+});
diff --git a/src/app/posts/state/posts.reducer.ts b/src/app/posts/state/posts.reducer.ts
new file mode 100644
index 0000000..88a6ec8
--- /dev/null
+++ b/src/app/posts/state/posts.reducer.ts
@@ -0,0 +1,58 @@
+import { createEntityAdapter, EntityState } from '@ngrx/entity';
+import { createFeature, createReducer, on } from '@ngrx/store';
+import { Post } from '../posts.model';
+import {
+ PostPageActions,
+ PostsApiActions,
+ PostsPageActions,
+} from './posts.actions';
+
+export interface PostsState extends EntityState {
+ loading: boolean;
+ loaded: boolean;
+ errorMessage: string;
+}
+
+const adapter = createEntityAdapter({});
+
+const initialState: PostsState = adapter.getInitialState({
+ loading: false,
+ loaded: false,
+ errorMessage: '',
+});
+
+export const postsFeature = createFeature({
+ name: 'posts',
+ reducer: createReducer(
+ initialState,
+ on(
+ PostsPageActions.pageOpened,
+ PostPageActions.pageOpened,
+ (state): PostsState => ({
+ ...state,
+ loading: !state.loaded,
+ errorMessage: '',
+ }),
+ ),
+ on(PostsApiActions.postsLoadedSuccess, (state, { posts }) =>
+ adapter.addMany(posts, {
+ ...state,
+ loading: false,
+ loaded: true,
+ }),
+ ),
+ on(
+ PostsApiActions.postsLoadedFail,
+ (state, { message }): PostsState => ({
+ ...state,
+ loading: false,
+ errorMessage: message,
+ }),
+ ),
+ ),
+});
+
+const { selectAll, selectEntities } = adapter.getSelectors();
+
+export const selectPosts = selectAll;
+export const selectPostsEntities = selectEntities;
diff --git a/src/app/posts/state/posts.selectors.spec.ts b/src/app/posts/state/posts.selectors.spec.ts
new file mode 100644
index 0000000..e133be4
--- /dev/null
+++ b/src/app/posts/state/posts.selectors.spec.ts
@@ -0,0 +1,101 @@
+import { Post } from '../posts.model';
+import { PostsState } from './posts.reducer';
+import {
+ selectPostById,
+ selectPosts,
+ selectPostsEntities,
+ selectPostsErrorMessage,
+ selectPostsLoaded,
+ selectPostsLoading,
+} from './posts.selectors';
+
+describe('Posts Selectors', () => {
+ const posts: Post[] = [
+ {
+ id: 1,
+ userId: 1,
+ title: 'Title 1',
+ body: 'Body 1',
+ },
+ {
+ id: 2,
+ userId: 2,
+ title: 'Title 2',
+ body: 'Body 2',
+ },
+ ];
+
+ const entities = {
+ 1: posts[0],
+ 2: posts[1],
+ };
+
+ const initialState: PostsState = {
+ ids: [1, 2],
+ entities,
+ loading: false,
+ loaded: false,
+ errorMessage: 'Error Message',
+ };
+
+ it('should select posts', () => {
+ const result = selectPosts.projector(initialState);
+ expect(result).toEqual(posts);
+ });
+
+ it('should select posts entities', () => {
+ const result = selectPostsEntities.projector(initialState);
+ expect(result).toEqual(entities);
+ });
+
+ it('should select post by id when route contains param', () => {
+ const result = selectPostById.projector(entities, '1');
+ expect(result).toEqual(posts[0]);
+ });
+
+ it('should return undefined when selecting post by id without route param', () => {
+ const result = selectPostById.projector(entities, undefined);
+ expect(result).toBeUndefined();
+ });
+
+ it('should select posts loading', () => {
+ const state1 = {
+ ...initialState,
+ loading: true,
+ };
+
+ const state2 = {
+ ...initialState,
+ loading: false,
+ };
+
+ const result1 = selectPostsLoading.projector(state1);
+ const result2 = selectPostsLoading.projector(state2);
+
+ expect(result1).toBeTrue();
+ expect(result2).toBeFalse();
+ });
+
+ it('should select posts loaded', () => {
+ const state1 = {
+ ...initialState,
+ loaded: true,
+ };
+
+ const state2 = {
+ ...initialState,
+ loaded: false,
+ };
+
+ const result1 = selectPostsLoaded.projector(state1);
+ const result2 = selectPostsLoaded.projector(state2);
+
+ expect(result1).toBeTrue();
+ expect(result2).toBeFalse();
+ });
+
+ it('should select posts error message', () => {
+ const result = selectPostsErrorMessage.projector(initialState);
+ expect(result).toEqual('Error Message');
+ });
+});
diff --git a/src/app/posts/state/posts.selectors.ts b/src/app/posts/state/posts.selectors.ts
new file mode 100644
index 0000000..0f81a30
--- /dev/null
+++ b/src/app/posts/state/posts.selectors.ts
@@ -0,0 +1,38 @@
+import { createFeatureSelector, createSelector } from '@ngrx/store';
+import { selectRouteParam } from 'src/app/router.selectors';
+import * as reducer from './posts.reducer';
+
+const selectPostsState = createFeatureSelector(
+ reducer.postsFeature.name,
+);
+
+export const selectPosts = createSelector(
+ selectPostsState,
+ reducer.selectPosts,
+);
+
+export const selectPostsEntities = createSelector(
+ selectPostsState,
+ reducer.selectPostsEntities,
+);
+
+export const selectPostById = createSelector(
+ selectPostsEntities,
+ selectRouteParam('id'),
+ (entities, id) => (id ? entities[id] : undefined),
+);
+
+export const selectPostsLoading = createSelector(
+ selectPostsState,
+ (state) => state.loading,
+);
+
+export const selectPostsLoaded = createSelector(
+ selectPostsState,
+ (state) => state.loaded,
+);
+
+export const selectPostsErrorMessage = createSelector(
+ selectPostsState,
+ (state) => state.errorMessage,
+);
diff --git a/src/app/router.selectors.ts b/src/app/router.selectors.ts
new file mode 100644
index 0000000..da0ee42
--- /dev/null
+++ b/src/app/router.selectors.ts
@@ -0,0 +1,3 @@
+import { getRouterSelectors } from '@ngrx/router-store';
+
+export const { selectRouteParam } = getRouterSelectors();
diff --git a/src/app/users/state/users.actions.ts b/src/app/users/state/users.actions.ts
new file mode 100644
index 0000000..209cb80
--- /dev/null
+++ b/src/app/users/state/users.actions.ts
@@ -0,0 +1,10 @@
+import { createActionGroup, props } from '@ngrx/store';
+import { User } from '../user.model';
+
+export const UsersApiActions = createActionGroup({
+ source: 'Users API',
+ events: {
+ 'Users Loaded Success': props<{ users: User[] }>(),
+ 'Users Loaded Fail': props<{ message: string }>(),
+ },
+});
diff --git a/src/app/users/state/users.effects.spec.ts b/src/app/users/state/users.effects.spec.ts
new file mode 100644
index 0000000..8bfabc3
--- /dev/null
+++ b/src/app/users/state/users.effects.spec.ts
@@ -0,0 +1,104 @@
+import { TestBed } from '@angular/core/testing';
+import { Actions } from '@ngrx/effects';
+import { provideMockActions } from '@ngrx/effects/testing';
+import { Action } from '@ngrx/store';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { Observable } from 'rxjs';
+import { TestScheduler } from 'rxjs/testing';
+import { PostsPageActions } from 'src/app/posts/state/posts.actions';
+import { User } from '../user.model';
+import { UsersService } from '../users.service';
+import { UsersApiActions } from './users.actions';
+import { UsersEffects } from './users.effects';
+import { selectUsersLoaded } from './users.selectors';
+
+describe('UsersEffects', () => {
+ let store: MockStore;
+ let mockUsersService: jasmine.SpyObj;
+ let testScheduler: TestScheduler;
+ let actions$ = new Observable();
+ let effects: UsersEffects;
+
+ beforeEach(() => {
+ mockUsersService = jasmine.createSpyObj(['getUsers']);
+ testScheduler = new TestScheduler((actual, expected) => {
+ expect(actual).toEqual(expected);
+ });
+
+ TestBed.configureTestingModule({
+ providers: [
+ provideMockActions(() => actions$),
+ provideMockStore({
+ selectors: [
+ {
+ selector: selectUsersLoaded,
+ value: false,
+ },
+ ],
+ }),
+ UsersEffects,
+ {
+ provide: UsersService,
+ useValue: mockUsersService,
+ },
+ ],
+ });
+
+ actions$ = TestBed.inject(Actions);
+ effects = TestBed.inject(UsersEffects);
+ store = TestBed.inject(MockStore);
+ });
+
+ describe('loadUsers$', () => {
+ it('should return loaded success on pageOpened', () => {
+ const users: User[] = [
+ {
+ id: 1,
+ name: 'User 1',
+ },
+ {
+ id: 2,
+ name: 'User 2',
+ },
+ ];
+
+ testScheduler.run(({ cold, hot, expectObservable }) => {
+ actions$ = hot('-a', {
+ a: PostsPageActions.pageOpened(),
+ });
+ mockUsersService.getUsers.and.returnValue(cold('--b|', { b: users }));
+
+ expectObservable(effects.loadUsers$).toBe('---c', {
+ c: UsersApiActions.usersLoadedSuccess({ users }),
+ });
+ });
+ });
+
+ it('should return loaded fail when service fails', () => {
+ const error = 'test error';
+ testScheduler.run(({ hot, cold, expectObservable }) => {
+ actions$ = hot('-a', { a: PostsPageActions.pageOpened() });
+ mockUsersService.getUsers.and.returnValue(
+ cold('--#', undefined, error),
+ );
+
+ expectObservable(effects.loadUsers$).toBe('---c', {
+ c: UsersApiActions.usersLoadedFail({
+ message: error,
+ }),
+ });
+ });
+ });
+
+ it('should not load entries when they were already loaded', () => {
+ store.overrideSelector(selectUsersLoaded, true);
+ testScheduler.run(({ hot, expectObservable }) => {
+ actions$ = hot('-a--', {
+ a: PostsPageActions.pageOpened(),
+ });
+
+ expectObservable(effects.loadUsers$).toBe('----');
+ });
+ });
+ });
+});
diff --git a/src/app/users/state/users.effects.ts b/src/app/users/state/users.effects.ts
new file mode 100644
index 0000000..28063b4
--- /dev/null
+++ b/src/app/users/state/users.effects.ts
@@ -0,0 +1,37 @@
+import { Injectable } from '@angular/core';
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { concatLatestFrom } from '@ngrx/operators';
+import { Store } from '@ngrx/store';
+import { catchError, exhaustMap, filter, map, of } from 'rxjs';
+import {
+ PostPageActions,
+ PostsPageActions,
+} from 'src/app/posts/state/posts.actions';
+import { UsersService } from '../users.service';
+import { UsersApiActions } from './users.actions';
+import { selectUsersLoaded } from './users.selectors';
+
+@Injectable()
+export class UsersEffects {
+ loadUsers$ = createEffect(() => {
+ return this.actions$.pipe(
+ ofType(PostsPageActions.pageOpened, PostPageActions.pageOpened),
+ concatLatestFrom(() => this.store.select(selectUsersLoaded)),
+ filter(([, loaded]) => !loaded),
+ exhaustMap(() =>
+ this.usersService.getUsers().pipe(
+ map((users) => UsersApiActions.usersLoadedSuccess({ users })),
+ catchError((error) =>
+ of(UsersApiActions.usersLoadedFail({ message: error })),
+ ),
+ ),
+ ),
+ );
+ });
+
+ constructor(
+ private actions$: Actions,
+ private store: Store,
+ private usersService: UsersService,
+ ) {}
+}
diff --git a/src/app/users/state/users.reducer.spec.ts b/src/app/users/state/users.reducer.spec.ts
new file mode 100644
index 0000000..d8d56be
--- /dev/null
+++ b/src/app/users/state/users.reducer.spec.ts
@@ -0,0 +1,160 @@
+import {
+ PostPageActions,
+ PostsPageActions,
+} from 'src/app/posts/state/posts.actions';
+import { User } from '../user.model';
+import { UsersApiActions } from './users.actions';
+import { usersFeature, UsersState } from './users.reducer';
+
+describe('Users Reducer', () => {
+ const DEFAULT_STATE: UsersState = {
+ ids: [],
+ entities: {},
+ loading: false,
+ loaded: false,
+ errorMessage: '',
+ };
+
+ const DEFAULT_USER: User = {
+ id: 1,
+ name: 'User 1',
+ };
+
+ describe('PostsPageAction.pageOpened action', () => {
+ it('should set loading to true when not loaded', () => {
+ const action = PostsPageActions.pageOpened();
+ const initialState: UsersState = {
+ ...DEFAULT_STATE,
+ };
+ const expectedState: UsersState = {
+ ...DEFAULT_STATE,
+ loading: true,
+ };
+
+ const result = usersFeature.reducer(initialState, action);
+
+ expect(result).toEqual(expectedState);
+ });
+
+ it('should set loading to false when loaded', () => {
+ const action = PostsPageActions.pageOpened();
+ const initialState: UsersState = {
+ ...DEFAULT_STATE,
+ loaded: true,
+ };
+ const expectedState: UsersState = {
+ ...initialState,
+ loading: false,
+ };
+
+ const result = usersFeature.reducer(initialState, action);
+
+ expect(result).toEqual(expectedState);
+ });
+
+ it('should reset the error message', () => {
+ const action = PostsPageActions.pageOpened();
+ const initialState: UsersState = {
+ ...DEFAULT_STATE,
+ errorMessage: 'Error Message',
+ loaded: true,
+ };
+
+ const result = usersFeature.reducer(initialState, action);
+
+ expect(result.errorMessage).toEqual('');
+ });
+ });
+
+ describe('PostPageAction.pageOpened action', () => {
+ it('should set loading to true when not loaded', () => {
+ const action = PostPageActions.pageOpened();
+ const initialState: UsersState = {
+ ...DEFAULT_STATE,
+ };
+ const expectedState: UsersState = {
+ ...DEFAULT_STATE,
+ loading: true,
+ };
+
+ const result = usersFeature.reducer(initialState, action);
+
+ expect(result).toEqual(expectedState);
+ });
+
+ it('should set loading to false when loaded', () => {
+ const action = PostPageActions.pageOpened();
+ const initialState: UsersState = {
+ ...DEFAULT_STATE,
+ loaded: true,
+ };
+ const expectedState: UsersState = {
+ ...initialState,
+ loading: false,
+ };
+
+ const result = usersFeature.reducer(initialState, action);
+
+ expect(result).toEqual(expectedState);
+ });
+
+ it('should reset the error message', () => {
+ const action = PostPageActions.pageOpened();
+ const initialState: UsersState = {
+ ...DEFAULT_STATE,
+ errorMessage: 'Error Message',
+ loaded: true,
+ };
+
+ const result = usersFeature.reducer(initialState, action);
+
+ expect(result.errorMessage).toEqual('');
+ });
+ });
+
+ describe('UsersApiActions.entriesLoadedSuccess', () => {
+ it('should set entities, loading to false and loaded to true', () => {
+ const action = UsersApiActions.usersLoadedSuccess({
+ users: [DEFAULT_USER],
+ });
+ const initialState: UsersState = {
+ ...DEFAULT_STATE,
+ loading: true,
+ loaded: false,
+ };
+ const expectedState: UsersState = {
+ ...DEFAULT_STATE,
+ ids: [1],
+ entities: {
+ 1: DEFAULT_USER,
+ },
+ loading: false,
+ loaded: true,
+ };
+
+ const result = usersFeature.reducer(initialState, action);
+
+ expect(result).toEqual(expectedState);
+ });
+ });
+
+ describe('UsersApiActions.entriesLoadedFail', () => {
+ it('should set error message and loading to false', () => {
+ const error = 'Error Message';
+ const action = UsersApiActions.usersLoadedFail({ message: error });
+ const initialState: UsersState = {
+ ...DEFAULT_STATE,
+ loading: true,
+ };
+ const expectedState: UsersState = {
+ ...DEFAULT_STATE,
+ loading: false,
+ errorMessage: error,
+ };
+
+ const result = usersFeature.reducer(initialState, action);
+
+ expect(result).toEqual(expectedState);
+ });
+ });
+});
diff --git a/src/app/users/state/users.reducer.ts b/src/app/users/state/users.reducer.ts
new file mode 100644
index 0000000..c722682
--- /dev/null
+++ b/src/app/users/state/users.reducer.ts
@@ -0,0 +1,58 @@
+import { createEntityAdapter, EntityState } from '@ngrx/entity';
+import { createFeature, createReducer, on } from '@ngrx/store';
+import {
+ PostPageActions,
+ PostsPageActions,
+} from 'src/app/posts/state/posts.actions';
+import { User } from '../user.model';
+import { UsersApiActions } from './users.actions';
+
+export interface UsersState extends EntityState {
+ loading: boolean;
+ loaded: boolean;
+ errorMessage: string;
+}
+
+const adapter = createEntityAdapter({});
+
+const initialState: UsersState = adapter.getInitialState({
+ loading: false,
+ loaded: false,
+ errorMessage: '',
+});
+
+export const usersFeature = createFeature({
+ name: 'users',
+ reducer: createReducer(
+ initialState,
+ on(
+ PostsPageActions.pageOpened,
+ PostPageActions.pageOpened,
+ (state): UsersState => ({
+ ...state,
+ loading: !state.loaded,
+ errorMessage: '',
+ }),
+ ),
+ on(UsersApiActions.usersLoadedSuccess, (state, { users }) =>
+ adapter.addMany(users, {
+ ...state,
+ loading: false,
+ loaded: true,
+ }),
+ ),
+ on(
+ UsersApiActions.usersLoadedFail,
+ (state, { message }): UsersState => ({
+ ...state,
+ loading: false,
+ errorMessage: message,
+ }),
+ ),
+ ),
+});
+
+const { selectAll, selectEntities } = adapter.getSelectors();
+
+export const selectUsers = selectAll;
+export const selectUsersEntities = selectEntities;
diff --git a/src/app/users/state/users.selectors.spec.ts b/src/app/users/state/users.selectors.spec.ts
new file mode 100644
index 0000000..a2970c0
--- /dev/null
+++ b/src/app/users/state/users.selectors.spec.ts
@@ -0,0 +1,78 @@
+import { User } from '../user.model';
+import { UsersState } from './users.reducer';
+import {
+ selectUsers,
+ selectUsersEntities,
+ selectUsersErrorMessage,
+ selectUsersLoaded,
+ selectUsersLoading,
+} from './users.selectors';
+
+describe('Users Selectors', () => {
+ const users: User[] = [
+ { id: 1, name: 'User 1' },
+ { id: 2, name: 'User 2' },
+ ];
+
+ const initialState: UsersState = {
+ ids: [1, 2],
+ entities: { 1: users[0], 2: users[1] },
+ loading: false,
+ loaded: false,
+ errorMessage: 'Error Message',
+ };
+
+ it('should select users', () => {
+ const result = selectUsers.projector(initialState);
+ expect(result).toEqual(users);
+ });
+
+ it('should select users entities', () => {
+ const result = selectUsersEntities.projector(initialState);
+ expect(result).toEqual({
+ 1: users[0],
+ 2: users[1],
+ });
+ });
+
+ it('should select users loading', () => {
+ const state1 = {
+ ...initialState,
+ loading: true,
+ };
+
+ const state2 = {
+ ...initialState,
+ loading: false,
+ };
+
+ const result1 = selectUsersLoading.projector(state1);
+ const result2 = selectUsersLoading.projector(state2);
+
+ expect(result1).toBeTrue();
+ expect(result2).toBeFalse();
+ });
+
+ it('should select users loaded', () => {
+ const state1 = {
+ ...initialState,
+ loaded: true,
+ };
+
+ const state2 = {
+ ...initialState,
+ loaded: false,
+ };
+
+ const result1 = selectUsersLoaded.projector(state1);
+ const result2 = selectUsersLoaded.projector(state2);
+
+ expect(result1).toBeTrue();
+ expect(result2).toBeFalse();
+ });
+
+ it('should select users error message', () => {
+ const result = selectUsersErrorMessage.projector(initialState);
+ expect(result).toEqual('Error Message');
+ });
+});
diff --git a/src/app/users/state/users.selectors.ts b/src/app/users/state/users.selectors.ts
new file mode 100644
index 0000000..1872c68
--- /dev/null
+++ b/src/app/users/state/users.selectors.ts
@@ -0,0 +1,31 @@
+import { createFeatureSelector, createSelector } from '@ngrx/store';
+import * as reducer from './users.reducer';
+
+const selectUsersState = createFeatureSelector(
+ reducer.usersFeature.name,
+);
+
+export const selectUsers = createSelector(
+ selectUsersState,
+ reducer.selectUsers,
+);
+
+export const selectUsersEntities = createSelector(
+ selectUsersState,
+ reducer.selectUsersEntities,
+);
+
+export const selectUsersLoading = createSelector(
+ selectUsersState,
+ (state) => state.loading,
+);
+
+export const selectUsersLoaded = createSelector(
+ selectUsersState,
+ (state) => state.loaded,
+);
+
+export const selectUsersErrorMessage = createSelector(
+ selectUsersState,
+ (state) => state.errorMessage,
+);
diff --git a/src/app/users/user.model.ts b/src/app/users/user.model.ts
new file mode 100644
index 0000000..4a77c78
--- /dev/null
+++ b/src/app/users/user.model.ts
@@ -0,0 +1,4 @@
+export interface User {
+ id: number;
+ name: string;
+}
diff --git a/src/app/users/users.service.spec.ts b/src/app/users/users.service.spec.ts
new file mode 100644
index 0000000..6c26c04
--- /dev/null
+++ b/src/app/users/users.service.spec.ts
@@ -0,0 +1,67 @@
+import { provideHttpClient } from '@angular/common/http';
+import {
+ HttpTestingController,
+ provideHttpClientTesting,
+} from '@angular/common/http/testing';
+import { TestBed } from '@angular/core/testing';
+import { firstValueFrom } from 'rxjs';
+import { User } from './user.model';
+import { UsersService } from './users.service';
+
+describe('UsersService', () => {
+ let http: HttpTestingController;
+ let service: UsersService;
+
+ let DEFAULT_USERS: User[];
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [
+ UsersService,
+ provideHttpClient(),
+ provideHttpClientTesting(),
+ ],
+ });
+
+ http = TestBed.inject(HttpTestingController);
+ service = TestBed.inject(UsersService);
+
+ DEFAULT_USERS = [
+ {
+ id: 1,
+ name: 'Test User 1',
+ },
+ {
+ id: 2,
+ name: 'Test User 2',
+ },
+ ];
+ });
+
+ afterEach(() => {
+ TestBed.inject(HttpTestingController).verify();
+ });
+
+ it('should return users array from getUsers', async () => {
+ const users$ = service.getUsers();
+ const usersPromise = firstValueFrom(users$);
+
+ const req = http.expectOne({
+ url: 'https://jsonplaceholder.typicode.com/users',
+ method: 'GET',
+ });
+ req.flush(DEFAULT_USERS);
+
+ expect(await usersPromise).toEqual(DEFAULT_USERS);
+ });
+
+ it('should return expected URL from getAvatarUrl', async () => {
+ const email = 'user@example.com';
+
+ const avatarUrl$ = service.getAvatarUrl(email);
+
+ expect(await firstValueFrom(avatarUrl$)).toBe(
+ 'https://0.gravatar.com/avatar/b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514?size=256&d=wavatar',
+ );
+ });
+});
diff --git a/src/app/users/users.service.ts b/src/app/users/users.service.ts
new file mode 100644
index 0000000..7f4a342
--- /dev/null
+++ b/src/app/users/users.service.ts
@@ -0,0 +1,40 @@
+import { HttpClient } from '@angular/common/http';
+import { inject, Injectable } from '@angular/core';
+import { catchError, from, map, Observable } from 'rxjs';
+import { handleError } from '../utils/http';
+import { User } from './user.model';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class UsersService {
+ private usersUrl = 'https://jsonplaceholder.typicode.com/users';
+ private http = inject(HttpClient);
+
+ getUsers(): Observable {
+ return this.http.get(this.usersUrl).pipe(catchError(handleError));
+ }
+
+ getAvatarUrl(email: string): Observable {
+ return from(this.getGravatarHash(email)).pipe(
+ map((hash) => {
+ return `https://0.gravatar.com/avatar/${hash}?size=256&d=wavatar`;
+ }),
+ );
+ }
+
+ private async getGravatarHash(email: string): Promise {
+ const cleanEmail = email.trim().toLowerCase();
+
+ const encoder = new TextEncoder();
+ const data = encoder.encode(cleanEmail);
+
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
+ const hashHex = hashArray
+ .map((b) => b.toString(16).padStart(2, '0'))
+ .join('');
+
+ return hashHex;
+ }
+}
diff --git a/src/app/utils/http.ts b/src/app/utils/http.ts
new file mode 100644
index 0000000..82a5739
--- /dev/null
+++ b/src/app/utils/http.ts
@@ -0,0 +1,6 @@
+import { HttpErrorResponse } from '@angular/common/http';
+import { throwError } from 'rxjs';
+
+export function handleError({ status }: HttpErrorResponse) {
+ return throwError(() => `${status}: An error occured.`);
+}
diff --git a/src/index.html b/src/index.html
index 98d504c..c6ef901 100644
--- a/src/index.html
+++ b/src/index.html
@@ -1,13 +1,21 @@
-
-
- AngularTemplate
-
-
-
-
-
-
-
+
+
+ Angular Training - Dmitrijs Savkins
+
+
+
+
+
+
+
+
+
diff --git a/src/main.ts b/src/main.ts
index 75a3f6a..1e9211f 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,9 +1,28 @@
+import { provideHttpClient } from '@angular/common/http';
+import { isDevMode } from '@angular/core';
+import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
+import { provideEffects } from '@ngrx/effects';
+import { provideRouterStore, routerReducer } from '@ngrx/router-store';
+import { provideStore } from '@ngrx/store';
+import { provideStoreDevtools } from '@ngrx/store-devtools';
import { appRoutes } from './app/app-routes';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
- providers: [provideRouter(appRoutes)]
-})
- .catch(err => console.error(err));
+ providers: [
+ provideHttpClient(),
+ provideRouter(appRoutes),
+ provideStore({
+ router: routerReducer,
+ }),
+ provideRouterStore(),
+ provideEffects(),
+ provideStoreDevtools({ maxAge: 25, logOnly: !isDevMode() }),
+ {
+ provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
+ useValue: { appearance: 'outline' },
+ },
+ ],
+}).catch((err) => console.error(err));
diff --git a/src/styles.scss b/src/styles.scss
index 90d4ee0..6fd1929 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -1 +1,11 @@
-/* You can add global styles to this file, and also import other style files */
+html,
+body {
+ height: 100%;
+}
+
+body {
+ margin: 0;
+ font-family: roboto, "helvetica neue", sans-serif;
+ background: var(--mat-sys-surface);
+ color: var(--mat-sys-on-surface);
+}
diff --git a/tsconfig.json b/tsconfig.json
index 4ec7ddc..738841a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -13,16 +13,15 @@
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"declaration": false,
+ "skipLibCheck": true,
+ "isolatedModules": true,
"experimentalDecorators": true,
- "moduleResolution": "node",
+ "moduleResolution": "bundler",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,
- "lib": [
- "ES2022",
- "dom"
- ]
+ "lib": ["ES2022", "dom"]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,