diff --git a/angular.json b/angular.json
index 91a9155..07d371a 100644
--- a/angular.json
+++ b/angular.json
@@ -27,9 +27,15 @@
"src/assets"
],
"styles": [
- "src/styles.css"
+ "src/styles.css",
+ "node_modules/bootstrap/dist/css/bootstrap.min.css",
+ "node_modules/ngx-toastr/toastr.css",
+ "node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css"
],
- "scripts": []
+ "scripts": [
+ "node_modules/bootstrap/dist/js/bootstrap.min.js",
+ "node_modules/jquery/dist/jquery.js"
+ ]
},
"configurations": {
"production": {
@@ -94,6 +100,7 @@
"src/assets"
],
"styles": [
+ "./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
"src/styles.css"
],
"scripts": []
diff --git a/package-lock.json b/package-lock.json
index a453960..401a9c1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,13 +9,25 @@
"version": "0.0.0",
"dependencies": {
"@angular/animations": "~13.3.0",
+ "@angular/cdk": "^13.3.6",
"@angular/common": "~13.3.0",
"@angular/compiler": "~13.3.0",
"@angular/core": "~13.3.0",
+ "@angular/flex-layout": "^13.0.0-beta.38",
"@angular/forms": "~13.3.0",
+ "@angular/material": "^13.3.6",
"@angular/platform-browser": "~13.3.0",
"@angular/platform-browser-dynamic": "~13.3.0",
"@angular/router": "~13.3.0",
+ "@fortawesome/angular-fontawesome": "^0.10.2",
+ "@fortawesome/fontawesome-svg-core": "^6.1.1",
+ "@fortawesome/free-solid-svg-icons": "^6.1.1",
+ "bootstrap": "^5.1.3",
+ "font-awesome": "^4.7.0",
+ "jquery": "^3.6.0",
+ "ngx-cookie-service": "^13.2.0",
+ "ngx-toastr": "^14.3.0",
+ "popper.js": "^1.16.1",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
@@ -347,6 +359,28 @@
"@angular/core": "13.3.5"
}
},
+ "node_modules/@angular/cdk": {
+ "version": "13.3.6",
+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.6.tgz",
+ "integrity": "sha512-sPwEGCHARxuUMzPLRiiN51GQP0P39ev+eL06NcyYXZ3AxyZIH5N/PWmGKf7EDOXIof4ata13jsIeW38mFe+wvg==",
+ "dependencies": {
+ "tslib": "^2.3.0"
+ },
+ "optionalDependencies": {
+ "parse5": "^5.0.0"
+ },
+ "peerDependencies": {
+ "@angular/common": "^13.0.0 || ^14.0.0-0",
+ "@angular/core": "^13.0.0 || ^14.0.0-0",
+ "rxjs": "^6.5.3 || ^7.4.0"
+ }
+ },
+ "node_modules/@angular/cdk/node_modules/parse5": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
+ "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
+ "optional": true
+ },
"node_modules/@angular/cli": {
"version": "13.3.4",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.4.tgz",
@@ -532,6 +566,21 @@
"zone.js": "~0.11.4"
}
},
+ "node_modules/@angular/flex-layout": {
+ "version": "13.0.0-beta.38",
+ "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-13.0.0-beta.38.tgz",
+ "integrity": "sha512-kcWb7CcoHbvw7fjo/knizWVmSSmvaTnr8v1ML6zOdxu1PK9UPPOcOS8RTm6fy61zoC2LABivP1/6Z2jF5XfpdQ==",
+ "dependencies": {
+ "tslib": "^2.3.0"
+ },
+ "peerDependencies": {
+ "@angular/cdk": "^13.0.0",
+ "@angular/common": "^13.0.0",
+ "@angular/core": "^13.0.0",
+ "@angular/platform-browser": "^13.0.0",
+ "rxjs": "^6.5.3 || ^7.4.0"
+ }
+ },
"node_modules/@angular/forms": {
"version": "13.3.5",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-13.3.5.tgz",
@@ -549,6 +598,23 @@
"rxjs": "^6.5.3 || ^7.4.0"
}
},
+ "node_modules/@angular/material": {
+ "version": "13.3.6",
+ "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.6.tgz",
+ "integrity": "sha512-V+3Fs9JK+7KlcdJG/Oa/IQuLHC8WYzuL8ffH1Q06ANSzGxSjsAHs4FZQpKUpVBoL3E+p9Yz+zkKe91k5L62VoQ==",
+ "dependencies": {
+ "tslib": "^2.3.0"
+ },
+ "peerDependencies": {
+ "@angular/animations": "^13.0.0 || ^14.0.0-0",
+ "@angular/cdk": "13.3.6",
+ "@angular/common": "^13.0.0 || ^14.0.0-0",
+ "@angular/core": "^13.0.0 || ^14.0.0-0",
+ "@angular/forms": "^13.0.0 || ^14.0.0-0",
+ "@angular/platform-browser": "^13.0.0 || ^14.0.0-0",
+ "rxjs": "^6.5.3 || ^7.4.0"
+ }
+ },
"node_modules/@angular/platform-browser": {
"version": "13.3.5",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-13.3.5.tgz",
@@ -2283,6 +2349,50 @@
"node": ">=10.0.0"
}
},
+ "node_modules/@fortawesome/angular-fontawesome": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.10.2.tgz",
+ "integrity": "sha512-VxsCAo2lK74KwD236AKAhGpiethfz9yqCViIG2iRAZqgNmuZ6ihwumjbLW32n6hV4fFvCqLcHmpngoEl3TNiOg==",
+ "dependencies": {
+ "tslib": "^2.3.1"
+ },
+ "peerDependencies": {
+ "@fortawesome/fontawesome-svg-core": "~1.2.27 || ~1.3.0-beta2 || ^6.1.0"
+ }
+ },
+ "node_modules/@fortawesome/fontawesome-common-types": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.1.1.tgz",
+ "integrity": "sha512-wVn5WJPirFTnzN6tR95abCx+ocH+3IFLXAgyavnf9hUmN0CfWoDjPT/BAWsUVwSlYYVBeCLJxaqi7ZGe4uSjBA==",
+ "hasInstallScript": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/fontawesome-svg-core": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.1.1.tgz",
+ "integrity": "sha512-NCg0w2YIp81f4V6cMGD9iomfsIj7GWrqmsa0ZsPh59G7PKiGN1KymZNxmF00ssuAlo/VZmpK6xazsGOwzKYUMg==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "6.1.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/free-solid-svg-icons": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.1.1.tgz",
+ "integrity": "sha512-0/5exxavOhI/D4Ovm2r3vxNojGZioPwmFrKg0ZUH69Q68uFhFPs6+dhAToh6VEQBntxPRYPuT5Cg1tpNa9JUPg==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "6.1.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/@gar/promisify": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
@@ -2509,6 +2619,16 @@
"read-package-json-fast": "^2.0.1"
}
},
+ "node_modules/@popperjs/core": {
+ "version": "2.11.5",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz",
+ "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==",
+ "peer": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
"node_modules/@schematics/angular": {
"version": "13.3.4",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.4.tgz",
@@ -3480,6 +3600,18 @@
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
"dev": true
},
+ "node_modules/bootstrap": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz",
+ "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/bootstrap"
+ },
+ "peerDependencies": {
+ "@popperjs/core": "^2.10.2"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -5704,6 +5836,14 @@
}
}
},
+ "node_modules/font-awesome": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
+ "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=",
+ "engines": {
+ "node": ">=0.10.3"
+ }
+ },
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -6902,6 +7042,11 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
+ "node_modules/jquery": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
+ "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw=="
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -7922,6 +8067,31 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
+ "node_modules/ngx-cookie-service": {
+ "version": "13.2.0",
+ "resolved": "https://registry.npmjs.org/ngx-cookie-service/-/ngx-cookie-service-13.2.0.tgz",
+ "integrity": "sha512-WxuLrZROWf59DfPPstPsrS18nxtPvT+uJ4AEjFs57NqtTfYdRQXhVJ02fZ4WP4VPElI8o6qndNL7gi9tkEdg4Q==",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@angular/common": "^13.0.0",
+ "@angular/core": "^13.0.0"
+ }
+ },
+ "node_modules/ngx-toastr": {
+ "version": "14.3.0",
+ "resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-14.3.0.tgz",
+ "integrity": "sha512-d8j/sOr60w5U7rGlcKQ0Ff4u+m2NzhqU5ZdJXn7QW3aR3Zf/rY7/Fd14BmUindTOWVr2NeTYcQXCjLpir0ldpA==",
+ "dependencies": {
+ "tslib": "^2.3.0"
+ },
+ "peerDependencies": {
+ "@angular/common": ">=12.0.0-0",
+ "@angular/core": ">=12.0.0-0",
+ "@angular/platform-browser": ">=12.0.0-0"
+ }
+ },
"node_modules/nice-napi": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
@@ -8880,6 +9050,16 @@
"node": ">=8"
}
},
+ "node_modules/popper.js": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
+ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
+ "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
"node_modules/portfinder": {
"version": "1.0.28",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
@@ -11886,6 +12066,23 @@
"tslib": "^2.3.0"
}
},
+ "@angular/cdk": {
+ "version": "13.3.6",
+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.6.tgz",
+ "integrity": "sha512-sPwEGCHARxuUMzPLRiiN51GQP0P39ev+eL06NcyYXZ3AxyZIH5N/PWmGKf7EDOXIof4ata13jsIeW38mFe+wvg==",
+ "requires": {
+ "parse5": "^5.0.0",
+ "tslib": "^2.3.0"
+ },
+ "dependencies": {
+ "parse5": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
+ "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
+ "optional": true
+ }
+ }
+ },
"@angular/cli": {
"version": "13.3.4",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.4.tgz",
@@ -12018,6 +12215,14 @@
"tslib": "^2.3.0"
}
},
+ "@angular/flex-layout": {
+ "version": "13.0.0-beta.38",
+ "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-13.0.0-beta.38.tgz",
+ "integrity": "sha512-kcWb7CcoHbvw7fjo/knizWVmSSmvaTnr8v1ML6zOdxu1PK9UPPOcOS8RTm6fy61zoC2LABivP1/6Z2jF5XfpdQ==",
+ "requires": {
+ "tslib": "^2.3.0"
+ }
+ },
"@angular/forms": {
"version": "13.3.5",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-13.3.5.tgz",
@@ -12026,6 +12231,14 @@
"tslib": "^2.3.0"
}
},
+ "@angular/material": {
+ "version": "13.3.6",
+ "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.6.tgz",
+ "integrity": "sha512-V+3Fs9JK+7KlcdJG/Oa/IQuLHC8WYzuL8ffH1Q06ANSzGxSjsAHs4FZQpKUpVBoL3E+p9Yz+zkKe91k5L62VoQ==",
+ "requires": {
+ "tslib": "^2.3.0"
+ }
+ },
"@angular/platform-browser": {
"version": "13.3.5",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-13.3.5.tgz",
@@ -13229,6 +13442,35 @@
"integrity": "sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA==",
"dev": true
},
+ "@fortawesome/angular-fontawesome": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.10.2.tgz",
+ "integrity": "sha512-VxsCAo2lK74KwD236AKAhGpiethfz9yqCViIG2iRAZqgNmuZ6ihwumjbLW32n6hV4fFvCqLcHmpngoEl3TNiOg==",
+ "requires": {
+ "tslib": "^2.3.1"
+ }
+ },
+ "@fortawesome/fontawesome-common-types": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.1.1.tgz",
+ "integrity": "sha512-wVn5WJPirFTnzN6tR95abCx+ocH+3IFLXAgyavnf9hUmN0CfWoDjPT/BAWsUVwSlYYVBeCLJxaqi7ZGe4uSjBA=="
+ },
+ "@fortawesome/fontawesome-svg-core": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.1.1.tgz",
+ "integrity": "sha512-NCg0w2YIp81f4V6cMGD9iomfsIj7GWrqmsa0ZsPh59G7PKiGN1KymZNxmF00ssuAlo/VZmpK6xazsGOwzKYUMg==",
+ "requires": {
+ "@fortawesome/fontawesome-common-types": "6.1.1"
+ }
+ },
+ "@fortawesome/free-solid-svg-icons": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.1.1.tgz",
+ "integrity": "sha512-0/5exxavOhI/D4Ovm2r3vxNojGZioPwmFrKg0ZUH69Q68uFhFPs6+dhAToh6VEQBntxPRYPuT5Cg1tpNa9JUPg==",
+ "requires": {
+ "@fortawesome/fontawesome-common-types": "6.1.1"
+ }
+ },
"@gar/promisify": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
@@ -13409,6 +13651,12 @@
"read-package-json-fast": "^2.0.1"
}
},
+ "@popperjs/core": {
+ "version": "2.11.5",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz",
+ "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==",
+ "peer": true
+ },
"@schematics/angular": {
"version": "13.3.4",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.4.tgz",
@@ -14225,6 +14473,12 @@
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
"dev": true
},
+ "bootstrap": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz",
+ "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==",
+ "requires": {}
+ },
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -15803,6 +16057,11 @@
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==",
"dev": true
},
+ "font-awesome": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
+ "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM="
+ },
"forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -16691,6 +16950,11 @@
}
}
},
+ "jquery": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
+ "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw=="
+ },
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -17459,6 +17723,22 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
+ "ngx-cookie-service": {
+ "version": "13.2.0",
+ "resolved": "https://registry.npmjs.org/ngx-cookie-service/-/ngx-cookie-service-13.2.0.tgz",
+ "integrity": "sha512-WxuLrZROWf59DfPPstPsrS18nxtPvT+uJ4AEjFs57NqtTfYdRQXhVJ02fZ4WP4VPElI8o6qndNL7gi9tkEdg4Q==",
+ "requires": {
+ "tslib": "^2.0.0"
+ }
+ },
+ "ngx-toastr": {
+ "version": "14.3.0",
+ "resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-14.3.0.tgz",
+ "integrity": "sha512-d8j/sOr60w5U7rGlcKQ0Ff4u+m2NzhqU5ZdJXn7QW3aR3Zf/rY7/Fd14BmUindTOWVr2NeTYcQXCjLpir0ldpA==",
+ "requires": {
+ "tslib": "^2.3.0"
+ }
+ },
"nice-napi": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
@@ -18189,6 +18469,11 @@
"find-up": "^4.0.0"
}
},
+ "popper.js": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
+ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
+ },
"portfinder": {
"version": "1.0.28",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
diff --git a/package.json b/package.json
index a747ea1..f74b884 100644
--- a/package.json
+++ b/package.json
@@ -11,13 +11,25 @@
"private": true,
"dependencies": {
"@angular/animations": "~13.3.0",
+ "@angular/cdk": "^13.3.6",
"@angular/common": "~13.3.0",
"@angular/compiler": "~13.3.0",
"@angular/core": "~13.3.0",
+ "@angular/flex-layout": "^13.0.0-beta.38",
"@angular/forms": "~13.3.0",
+ "@angular/material": "^13.3.6",
"@angular/platform-browser": "~13.3.0",
"@angular/platform-browser-dynamic": "~13.3.0",
"@angular/router": "~13.3.0",
+ "@fortawesome/angular-fontawesome": "^0.10.2",
+ "@fortawesome/fontawesome-svg-core": "^6.1.1",
+ "@fortawesome/free-solid-svg-icons": "^6.1.1",
+ "bootstrap": "^5.1.3",
+ "font-awesome": "^4.7.0",
+ "jquery": "^3.6.0",
+ "ngx-cookie-service": "^13.2.0",
+ "ngx-toastr": "^14.3.0",
+ "popper.js": "^1.16.1",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 0297262..16296bb 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -1,10 +1,47 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
+import { HomeComponent } from './components/home/home.component';
+import { LoginComponent } from './components/login/login.component';
+import { UserEditComponent } from './components/workspace/user-edit/user-edit.component';
+import { WorkspaceComponent } from './components/workspace/workspace.component';
+import { UsersListComponent } from './components/workspace/users-list/users-list.component';
+import { UserItemComponent } from './components/workspace/users-list/user-item/user-item.component';
+import { MessagesComponent } from './components/workspace/messages/messages.component';
+import { ProfileService } from './services/profile/profile.service';
-const routes: Routes = [];
+const routes: Routes = [
+ {
+ path:"login",
+ component:LoginComponent
+ },
+ {
+ path:"",
+ component:HomeComponent,
+ children: [
+ {
+ path:"edit/:id",
+ component:UserEditComponent
+ },
+ {
+ path:"utilisateurs",
+ component:UsersListComponent
+ },
+ {
+ path:"messages",
+ component:MessagesComponent
+ }
+ ],
+ canActivate: [ProfileService]
+ },
+ {
+ path:'**',
+ redirectTo:'/'
+ }
+];
@NgModule({
- imports: [RouterModule.forRoot(routes)],
- exports: [RouterModule]
+ imports: [RouterModule.forRoot(routes)],
+ exports: [RouterModule]
})
+
export class AppRoutingModule { }
diff --git a/src/app/app.component.html b/src/app/app.component.html
index e11ca59..0680b43 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,484 +1 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{{ 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?
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
ng generate component xyz
-
ng add @angular/material
-
ng add @angular/pwa
-
ng add _____
-
ng test
-
ng build
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index b1c6c96..04ca756 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -1,18 +1,92 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
+import { HttpClientModule, HttpClientXsrfModule, HTTP_INTERCEPTORS } from '@angular/common/http';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { CookieService } from 'ngx-cookie-service';
+import { RouterModule } from '@angular/router';
+
+import { MatToolbarModule } from '@angular/material/toolbar';
+import { MatSidenavModule } from '@angular/material/sidenav';
+import { MatButtonModule } from '@angular/material/button';
+import { MatIconModule } from '@angular/material/icon';
+import { MatDividerModule } from '@angular/material/divider';
+import { MatDialogModule } from '@angular/material/dialog';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
+import { MatCardModule } from '@angular/material/card';
+import { MatPaginatorModule } from '@angular/material/paginator';
+import { MatSortModule } from '@angular/material/sort';
+import { MatTableModule } from "@angular/material/table";
+import { MatSelectModule } from '@angular/material/select';
+import { MatCheckboxModule } from '@angular/material/checkbox';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
+import { LoginComponent } from './components/login/login.component';
+import { HomeComponent } from './components/home/home.component';
+import { HttpXsrfInterceptorService } from './interceptors/http-xsrf-interceptor/http-xsrf-interceptor.service';
+import { NavbarComponent } from './components/navbar/navbar.component';
+import { WorkspaceComponent } from './components/workspace/workspace.component';
+import { UserEditComponent } from './components/workspace/user-edit/user-edit.component';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { FlexLayoutModule } from '@angular/flex-layout';
+import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
+
+import { ToastrModule } from 'ngx-toastr';
+import { UsersListComponent } from './components/workspace/users-list/users-list.component';
+import { UserItemComponent } from './components/workspace/users-list/user-item/user-item.component';
+import { ConfirmDialogComponent } from './components/dialog/confirm-dialog/confirm-dialog.component';
+import { AddUserDialogComponent } from './components/dialog/add-user-dialog/add-user-dialog.component';
+import { MessagesComponent } from './components/workspace/messages/messages.component';
+import { ChatboxComponent } from './components/workspace/messages/chatbox/chatbox.component';
@NgModule({
- declarations: [
- AppComponent
- ],
- imports: [
- BrowserModule,
- AppRoutingModule
- ],
- providers: [],
- bootstrap: [AppComponent]
+ declarations: [
+ AppComponent,
+ LoginComponent,
+ HomeComponent,
+ NavbarComponent,
+ WorkspaceComponent,
+ UserEditComponent,
+ UsersListComponent,
+ UserItemComponent,
+ ConfirmDialogComponent,
+ AddUserDialogComponent,
+ MessagesComponent,
+ ChatboxComponent,
+ ],
+ imports: [
+ BrowserModule,
+ AppRoutingModule,
+ HttpClientModule,
+ FormsModule,
+ ReactiveFormsModule,
+ HttpClientXsrfModule.withOptions({
+ cookieName: 'csrf_access_token'
+ }),
+ BrowserAnimationsModule,
+ MatToolbarModule,
+ MatSidenavModule,
+ MatIconModule,
+ MatDividerModule,
+ MatDialogModule,
+ MatFormFieldModule,
+ MatInputModule,
+ MatButtonModule,
+ MatCardModule,
+ MatPaginatorModule,
+ MatSortModule,
+ MatTableModule,
+ MatSelectModule,
+ MatCheckboxModule,
+ FlexLayoutModule,
+ RouterModule,
+ FontAwesomeModule,
+ ToastrModule.forRoot()
+ ],
+ providers: [
+ { provide: HTTP_INTERCEPTORS, useClass: HttpXsrfInterceptorService, multi: true }
+ ],
+ bootstrap: [AppComponent]
})
export class AppModule { }
diff --git a/src/app/components/dialog/add-user-dialog/add-user-dialog.component.css b/src/app/components/dialog/add-user-dialog/add-user-dialog.component.css
new file mode 100644
index 0000000..5516501
--- /dev/null
+++ b/src/app/components/dialog/add-user-dialog/add-user-dialog.component.css
@@ -0,0 +1,6 @@
+.mat-form-field {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+}
diff --git a/src/app/components/dialog/add-user-dialog/add-user-dialog.component.html b/src/app/components/dialog/add-user-dialog/add-user-dialog.component.html
new file mode 100644
index 0000000..d5d1c11
--- /dev/null
+++ b/src/app/components/dialog/add-user-dialog/add-user-dialog.component.html
@@ -0,0 +1,29 @@
+Ajout d'un utilisateur
+
+
+
+
+
+
+
+
+
diff --git a/src/app/components/dialog/add-user-dialog/add-user-dialog.component.spec.ts b/src/app/components/dialog/add-user-dialog/add-user-dialog.component.spec.ts
new file mode 100644
index 0000000..2b92fda
--- /dev/null
+++ b/src/app/components/dialog/add-user-dialog/add-user-dialog.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AddUserDialogComponent } from './add-user-dialog.component';
+
+describe('AddUserDialogComponent', () => {
+ let component: AddUserDialogComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ AddUserDialogComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(AddUserDialogComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/dialog/add-user-dialog/add-user-dialog.component.ts b/src/app/components/dialog/add-user-dialog/add-user-dialog.component.ts
new file mode 100644
index 0000000..ccdae2c
--- /dev/null
+++ b/src/app/components/dialog/add-user-dialog/add-user-dialog.component.ts
@@ -0,0 +1,41 @@
+import { Component, OnInit, Inject } from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+import { FormBuilder, FormGroup, Validators, ValidatorFn, ValidationErrors, AbstractControl } from '@angular/forms';
+import { Router } from '@angular/router';
+import { BackendService } from '../../../services/backend.service';
+import { Utilisateur } from '../../../model/utilisateur.model';
+
+@Component({
+ selector: 'app-add-user-dialog',
+ templateUrl: './add-user-dialog.component.html',
+ styleUrls: ['./add-user-dialog.component.css']
+})
+export class AddUserDialogComponent implements OnInit {
+ userFG:FormGroup;
+ submitted:boolean=false;
+ selected:string="Coach";
+ constructor(private fb:FormBuilder,
+ private bs:BackendService,
+ private router:Router,
+ @Inject(MAT_DIALOG_DATA) public data:any,
+ public dialogRef: MatDialogRef) { }
+
+ ngOnInit(): void {
+ this.userFG=this.fb.group({
+ nom:["", Validators.required],
+ prenom:["", Validators.required],
+ identifiant:["", Validators.required],
+ role:[""],
+ });
+
+ this.userFG.get('role')?.setValue("Coach");
+ }
+
+ sendUser(user:FormGroup): void {
+ this.dialogRef.close(user.value);
+ }
+
+ onAbort(): void {
+ this.dialogRef.close();
+ }
+}
diff --git a/src/app/components/dialog/confirm-dialog/confirm-dialog.component.css b/src/app/components/dialog/confirm-dialog/confirm-dialog.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/components/dialog/confirm-dialog/confirm-dialog.component.html b/src/app/components/dialog/confirm-dialog/confirm-dialog.component.html
new file mode 100644
index 0000000..43f91e5
--- /dev/null
+++ b/src/app/components/dialog/confirm-dialog/confirm-dialog.component.html
@@ -0,0 +1,8 @@
+{{title}}
+
+ {{text}}
+
+
+
+
+
diff --git a/src/app/components/dialog/confirm-dialog/confirm-dialog.component.spec.ts b/src/app/components/dialog/confirm-dialog/confirm-dialog.component.spec.ts
new file mode 100644
index 0000000..fe08dc5
--- /dev/null
+++ b/src/app/components/dialog/confirm-dialog/confirm-dialog.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ConfirmDialogComponent } from './confirm-dialog.component';
+
+describe('ConfirmDialogComponent', () => {
+ let component: ConfirmDialogComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ ConfirmDialogComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ConfirmDialogComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/dialog/confirm-dialog/confirm-dialog.component.ts b/src/app/components/dialog/confirm-dialog/confirm-dialog.component.ts
new file mode 100644
index 0000000..6502530
--- /dev/null
+++ b/src/app/components/dialog/confirm-dialog/confirm-dialog.component.ts
@@ -0,0 +1,31 @@
+import { Component, OnInit, Inject } from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+
+@Component({
+ selector: 'app-confirm-dialog',
+ templateUrl: './confirm-dialog.component.html',
+ styleUrls: ['./confirm-dialog.component.css']
+})
+export class ConfirmDialogComponent implements OnInit {
+ title:string;
+ text:string;
+ labelOK: string;
+ labelNOK: string;
+
+ constructor(
+ @Inject(MAT_DIALOG_DATA) public data:any,
+ public dialogRef: MatDialogRef
+ ) {
+ this.title = data.title;
+ this.text = data.text;
+ this.labelOK = data.labelOK;
+ this.labelNOK = data.labelNOK;
+ }
+
+ ngOnInit(): void {
+ }
+
+ sendAnswer(val:boolean): void {
+ this.dialogRef.close(val);
+ }
+}
diff --git a/src/app/components/home/home.component.css b/src/app/components/home/home.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/components/home/home.component.html b/src/app/components/home/home.component.html
new file mode 100644
index 0000000..998fd52
--- /dev/null
+++ b/src/app/components/home/home.component.html
@@ -0,0 +1 @@
+
diff --git a/src/app/components/home/home.component.spec.ts b/src/app/components/home/home.component.spec.ts
new file mode 100644
index 0000000..2c5a172
--- /dev/null
+++ b/src/app/components/home/home.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { HomeComponent } from './home.component';
+
+describe('HomeComponent', () => {
+ let component: HomeComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ HomeComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(HomeComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/home/home.component.ts b/src/app/components/home/home.component.ts
new file mode 100644
index 0000000..16ce624
--- /dev/null
+++ b/src/app/components/home/home.component.ts
@@ -0,0 +1,59 @@
+import { Component, OnInit } from '@angular/core';
+import { Utilisateur } from '../../model/utilisateur.model';
+import { BackendService } from '../../services/backend.service';
+import { EventDriverService } from '../../services/event.driver.service';
+import { ActionsTypes, ActionEvt } from '../../state/user.state';
+import { Observable, of, Subscription } from 'rxjs';
+import { catchError, map, startWith } from 'rxjs/operators';
+import { ToastrService } from 'ngx-toastr';
+
+@Component({
+ selector: 'app-home',
+ templateUrl: './home.component.html',
+ styleUrls: ['./home.component.css']
+})
+export class HomeComponent implements OnInit {
+ profile$:Observable|null=null;
+ sub:Subscription;
+
+ constructor( private bs:BackendService,
+ private evtDrv:EventDriverService,
+ private toast:ToastrService ) { }
+
+ ngOnInit(): void {
+ this.sub=this.evtDrv.sourceEventSubjectObservable.subscribe(
+ (evt:ActionEvt) => {
+ this.onActionEvent(evt);
+ }, error => {
+ this.toast.error("Une erreur s'est produite ...");
+ }
+ );
+
+ this.profile$ = this.bs.getCurrentUser().pipe(
+ map(data => {
+ return data;
+ })
+ );
+ }
+
+ ngOnDestroy(): void {
+ this.sub.unsubscribe();
+ }
+
+ onActionEvent($evt:ActionEvt): void {
+ console.log("[HOME] ", $evt.type);
+ switch($evt.type) {
+ case ActionsTypes.UPDATE_USER: {
+ this.profile$ = this.bs.getUser($evt.payload.userId).pipe(
+ map(data => {
+ return data;
+ }));
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ }
+
+}
diff --git a/src/app/components/login/login.component.css b/src/app/components/login/login.component.css
new file mode 100644
index 0000000..d0b6bfe
--- /dev/null
+++ b/src/app/components/login/login.component.css
@@ -0,0 +1,299 @@
+
+/* BASIC */
+
+html {
+ background-color: #56baed;
+}
+
+body {
+ font-family: "Poppins", sans-serif;
+ height: 100vh;
+}
+
+a {
+ color: #92badd;
+ display:inline-block;
+ text-decoration: none;
+ font-weight: 400;
+}
+
+h2 {
+ text-align: center;
+ font-size: 16px;
+ font-weight: 600;
+ text-transform: uppercase;
+ display:inline-block;
+ margin: 40px 8px 10px 8px;
+ color: #cccccc;
+}
+
+h4 {
+ text-align: center;
+ font-size: 20px;
+ font-weight: 600;
+ text-transform: uppercase;
+ display:inline-block;
+ margin: 40px 8px 10px 8px;
+ color: #cccccc;
+}
+
+
+/* STRUCTURE */
+
+.wrapper {
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ justify-content: center;
+ width: 100%;
+ min-height: 100%;
+ padding: 20px;
+}
+
+#formContent {
+ -webkit-border-radius: 10px 10px 10px 10px;
+ border-radius: 10px 10px 10px 10px;
+ background: #fff;
+ padding: 30px;
+ width: 90%;
+ max-width: 450px;
+ position: relative;
+ padding: 0px;
+ -webkit-box-shadow: 0 30px 60px 0 rgba(0,0,0,0.3);
+ box-shadow: 0 30px 60px 0 rgba(0,0,0,0.3);
+ text-align: center;
+}
+
+#formFooter {
+ background-color: #f6f6f6;
+ border-top: 1px solid #dce8f1;
+ padding: 25px;
+ text-align: center;
+ -webkit-border-radius: 0 0 10px 10px;
+ border-radius: 0 0 10px 10px;
+}
+
+
+
+/* TABS */
+
+h2.inactive {
+ color: #cccccc;
+}
+
+h2.active {
+ color: #0d0d0d;
+ border-bottom: 2px solid #5fbae9;
+}
+
+
+
+/* FORM TYPOGRAPHY*/
+
+input[type=button], input[type=submit], input[type=reset] {
+ background-color: #56baed;
+ border: none;
+ color: white;
+ padding: 15px 80px;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ text-transform: uppercase;
+ font-size: 13px;
+ -webkit-box-shadow: 0 10px 30px 0 rgba(95,186,233,0.4);
+ box-shadow: 0 10px 30px 0 rgba(95,186,233,0.4);
+ -webkit-border-radius: 5px 5px 5px 5px;
+ border-radius: 5px 5px 5px 5px;
+ margin: 5px 20px 40px 20px;
+ -webkit-transition: all 0.3s ease-in-out;
+ -moz-transition: all 0.3s ease-in-out;
+ -ms-transition: all 0.3s ease-in-out;
+ -o-transition: all 0.3s ease-in-out;
+ transition: all 0.3s ease-in-out;
+}
+
+input[type=button]:hover, input[type=submit]:hover, input[type=reset]:hover {
+ background-color: #39ace7;
+}
+
+input[type=button]:active, input[type=submit]:active, input[type=reset]:active {
+ -moz-transform: scale(0.95);
+ -webkit-transform: scale(0.95);
+ -o-transform: scale(0.95);
+ -ms-transform: scale(0.95);
+ transform: scale(0.95);
+}
+
+input[type=text] {
+ background-color: #f6f6f6;
+ border: none;
+ color: #0d0d0d;
+ padding: 15px 32px;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ font-size: 16px;
+ margin: 5px;
+ width: 85%;
+ border: 2px solid #f6f6f6;
+ -webkit-transition: all 0.5s ease-in-out;
+ -moz-transition: all 0.5s ease-in-out;
+ -ms-transition: all 0.5s ease-in-out;
+ -o-transition: all 0.5s ease-in-out;
+ transition: all 0.5s ease-in-out;
+ -webkit-border-radius: 5px 5px 5px 5px;
+ border-radius: 5px 5px 5px 5px;
+}
+
+input[type=text]:focus {
+ background-color: #fff;
+ border-bottom: 2px solid #5fbae9;
+}
+
+input[type=text]:placeholder {
+ color: #cccccc;
+}
+
+
+input[type=password] {
+ background-color: #f6f6f6;
+ border: none;
+ color: #0d0d0d;
+ padding: 15px 32px;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ font-size: 16px;
+ margin: 5px;
+ width: 85%;
+ border: 2px solid #f6f6f6;
+ -webkit-transition: all 0.5s ease-in-out;
+ -moz-transition: all 0.5s ease-in-out;
+ -ms-transition: all 0.5s ease-in-out;
+ -o-transition: all 0.5s ease-in-out;
+ transition: all 0.5s ease-in-out;
+ -webkit-border-radius: 5px 5px 5px 5px;
+ border-radius: 5px 5px 5px 5px;
+}
+
+input[type=password]:focus {
+ background-color: #fff;
+ border-bottom: 2px solid #5fbae9;
+}
+
+input[type=password]:placeholder {
+ color: #cccccc;
+}
+
+/* ANIMATIONS */
+
+/* Simple CSS3 Fade-in-down Animation */
+.fadeInDown {
+ -webkit-animation-name: fadeInDown;
+ animation-name: fadeInDown;
+ -webkit-animation-duration: 1s;
+ animation-duration: 1s;
+ -webkit-animation-fill-mode: both;
+ animation-fill-mode: both;
+}
+
+@-webkit-keyframes fadeInDown {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translate3d(0, -100%, 0);
+ transform: translate3d(0, -100%, 0);
+ }
+ 100% {
+ opacity: 1;
+ -webkit-transform: none;
+ transform: none;
+ }
+}
+
+@keyframes fadeInDown {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translate3d(0, -100%, 0);
+ transform: translate3d(0, -100%, 0);
+ }
+ 100% {
+ opacity: 1;
+ -webkit-transform: none;
+ transform: none;
+ }
+}
+
+/* Simple CSS3 Fade-in Animation */
+@-webkit-keyframes fadeIn { from { opacity:0; } to { opacity:1; } }
+@-moz-keyframes fadeIn { from { opacity:0; } to { opacity:1; } }
+@keyframes fadeIn { from { opacity:0; } to { opacity:1; } }
+
+.fadeIn {
+ opacity:0;
+ -webkit-animation:fadeIn ease-in 1;
+ -moz-animation:fadeIn ease-in 1;
+ animation:fadeIn ease-in 1;
+
+ -webkit-animation-fill-mode:forwards;
+ -moz-animation-fill-mode:forwards;
+ animation-fill-mode:forwards;
+
+ -webkit-animation-duration:1s;
+ -moz-animation-duration:1s;
+ animation-duration:1s;
+}
+
+.fadeIn.first {
+ -webkit-animation-delay: 0.4s;
+ -moz-animation-delay: 0.4s;
+ animation-delay: 0.4s;
+}
+
+.fadeIn.second {
+ -webkit-animation-delay: 0.6s;
+ -moz-animation-delay: 0.6s;
+ animation-delay: 0.6s;
+}
+
+.fadeIn.third {
+ -webkit-animation-delay: 0.8s;
+ -moz-animation-delay: 0.8s;
+ animation-delay: 0.8s;
+}
+
+.fadeIn.fourth {
+ -webkit-animation-delay: 1s;
+ -moz-animation-delay: 1s;
+ animation-delay: 1s;
+}
+
+/* Simple CSS3 Fade-in Animation */
+.underlineHover:after {
+ display: block;
+ left: 0;
+ bottom: -10px;
+ width: 0;
+ height: 2px;
+ background-color: #56baed;
+ content: "";
+ transition: width 0.2s;
+}
+
+.underlineHover:hover {
+ color: #0d0d0d;
+}
+
+.underlineHover:hover:after{
+ width: 100%;
+}
+
+/* OTHERS */
+
+*:focus {
+ outline: none;
+}
+
+#icon {
+ width:60%;
+}
+
diff --git a/src/app/components/login/login.component.html b/src/app/components/login/login.component.html
new file mode 100644
index 0000000..591386a
--- /dev/null
+++ b/src/app/components/login/login.component.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
U10 Manager
+
+
+
+
+
+
diff --git a/src/app/components/login/login.component.spec.ts b/src/app/components/login/login.component.spec.ts
new file mode 100644
index 0000000..d2c0e6c
--- /dev/null
+++ b/src/app/components/login/login.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LoginComponent } from './login.component';
+
+describe('LoginComponent', () => {
+ let component: LoginComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ LoginComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(LoginComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/login/login.component.ts b/src/app/components/login/login.component.ts
new file mode 100644
index 0000000..5395c9d
--- /dev/null
+++ b/src/app/components/login/login.component.ts
@@ -0,0 +1,42 @@
+import { Component, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { Router } from '@angular/router';
+import { BackendService } from '../../services/backend.service';
+
+@Component({
+ selector: 'app-login',
+ templateUrl: './login.component.html',
+ styleUrls: ['./login.component.css']
+})
+export class LoginComponent implements OnInit {
+ loginFG?:FormGroup;
+ submitted:boolean=false;
+
+ constructor(private fb:FormBuilder, private bs:BackendService, private router:Router) { }
+
+ ngOnInit(): void {
+ this.loginFG=this.fb.group({
+ login:["", Validators.required],
+ password:["", Validators.required],
+ });
+ }
+
+ onLogin(): void {
+ this.submitted=true;
+ if(this.loginFG?.invalid) {
+ return;
+ }
+ // DEBUG
+ console.log(this.loginFG?.value);
+ // END DEBUG
+ this.bs.loginUser(this.loginFG?.value).subscribe(
+ data=>{
+ // DEBUG
+ console.log(data);
+ // END DEBUG
+ this.router.navigateByUrl("/");
+ }, error=>{
+ alert("Credentials failed, please try again !");
+ });
+ }
+}
diff --git a/src/app/components/navbar/navbar.component.css b/src/app/components/navbar/navbar.component.css
new file mode 100644
index 0000000..22b2d20
--- /dev/null
+++ b/src/app/components/navbar/navbar.component.css
@@ -0,0 +1,153 @@
+mat-toolbar {
+ background: #004a9f;
+ color: white;
+}
+
+mat-sidenav {
+ margin: 16px;
+ width: 250px;
+ border-right: none;
+ background: #002b5c;
+ color: white;
+ border-radius: 10px;
+ padding: 16px;
+ text-align: center;
+}
+
+.content {
+ /* height: calc(100vh - 130px); */
+ height: calc(100vh - 40px);
+ border-radius: 10px;
+ margin: 16px;
+ /* margin-left: 32px; */
+ margin-left: 32px;
+
+ padding: 16px;
+
+ overflow: hidden;
+}
+
+mat-sidenav-container {
+ height: calc(100vh - 5px);
+}
+
+.menu-button {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ font-size: 1rem;
+
+ mat-icon {
+ margin-right: 8px;
+ }
+}
+
+.avatar {
+ margin-top: 16px;
+ width: 100px;
+ height: 100px;
+ border-radius: 50%;
+}
+
+.name {
+ margin-top: 8px;
+ font-weight: normal;
+}
+
+.designation {
+ margin-top: 2px;
+ font-size: 0.7rem;
+ color: lightgrey;
+}
+
+mat-divider {
+ margin-top: 16px;
+ margin-bottom: 16px;
+ background-color: rgba(255, 255, 255, 0.5);
+}
+
+mat-icon {
+ margin: 5px 5px 5px 5px;
+}
+
+/*
+*
+* ==========================================
+* CUSTOM UTIL CLASSES
+* ==========================================
+*
+* .vertical-nav {
+* min-width: 17rem;
+* width: 17rem;
+* height: 100vh;
+* position: fixed;
+* top: 0;
+* left: 0;
+* box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.1);
+* transition: all 0.4s;
+* }
+*
+* .page-content {
+* width: calc(100% - 17rem);
+* margin-left: 17rem;
+* transition: all 0.4s;
+* }
+*
+* // for toggle behavior
+*
+* #sidebar.active {
+* margin-left: -17rem;
+* }
+*
+* #content.active {
+* width: 100%;
+* margin: 0;
+* }
+*
+* @media (max-width: 768px) {
+* #sidebar {
+* margin-left: -17rem;
+* }
+* #sidebar.active {
+* margin-left: 0;
+* }
+* #content {
+* width: 100%;
+* margin: 0;
+* }
+* #content.active {
+* margin-left: 17rem;
+* width: calc(100% - 17rem);
+* }
+* }
+*
+* /*
+* *
+* * ==========================================
+* * FOR DEMO PURPOSE
+* * ==========================================
+* *
+*
+*
+* body {
+* background: #599fd9;
+* background: -webkit-linear-gradient(to right, #599fd9, #c2e59c);
+* background: linear-gradient(to right, #599fd9, #c2e59c);
+* min-height: 100vh;
+* overflow-x: hidden;
+* }
+*
+* .separator {
+* margin: 3rem 0;
+* border-bottom: 1px dashed #fff;
+* }
+*
+* .text-uppercase {
+* letter-spacing: 0.1em;
+* }
+*
+* .text-gray {
+* color: #aaa;
+* }
+*/
diff --git a/src/app/components/navbar/navbar.component.html b/src/app/components/navbar/navbar.component.html
new file mode 100644
index 0000000..9abd0a0
--- /dev/null
+++ b/src/app/components/navbar/navbar.component.html
@@ -0,0 +1,59 @@
+
+
+
+
+
+ {{profile.Prenom}} {{profile.Nom}}
+ {{profile.Role}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/components/navbar/navbar.component.spec.ts b/src/app/components/navbar/navbar.component.spec.ts
new file mode 100644
index 0000000..f8ccd6f
--- /dev/null
+++ b/src/app/components/navbar/navbar.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { NavbarComponent } from './navbar.component';
+
+describe('NavbarComponent', () => {
+ let component: NavbarComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ NavbarComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(NavbarComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/navbar/navbar.component.ts b/src/app/components/navbar/navbar.component.ts
new file mode 100644
index 0000000..a09d855
--- /dev/null
+++ b/src/app/components/navbar/navbar.component.ts
@@ -0,0 +1,89 @@
+import { Component, OnInit, Input, Output, ViewChild } from '@angular/core';
+import { Utilisateur } from '../../model/utilisateur.model';
+import { BackendService } from '../../services/backend.service';
+import { ActionsTypes, ActionEvt } from '../../state/user.state';
+import { EventDriverService } from '../../services/event.driver.service';
+import { Observable, Subscription } from 'rxjs';
+import { catchError, map, startWith } from 'rxjs/operators';
+import { Router, NavigationEnd } from '@angular/router';
+import { BreakpointObserver } from '@angular/cdk/layout';
+import { MatSidenav } from '@angular/material/sidenav';
+import { delay, filter } from 'rxjs/operators';
+import { ToastrService } from 'ngx-toastr';
+
+@Component({
+ selector: 'app-navbar',
+ templateUrl: './navbar.component.html',
+ styleUrls: ['./navbar.component.css']
+})
+export class NavbarComponent implements OnInit {
+ @Input() userInput$:Observable|null=null;
+ userId:number;
+ thumbnail:any;
+ isAdmin:boolean = false;
+ @ViewChild(MatSidenav)
+ sidenav!: MatSidenav;
+
+ constructor( private bs:BackendService,
+ private router:Router,
+ private evtDrv:EventDriverService,
+ private observer: BreakpointObserver,
+ private toast:ToastrService ) {}
+
+ ngOnInit(): void {
+ this.userInput$?.subscribe(
+ (observer:Utilisateur) => {
+ this.userId = observer['userId'];
+ if(observer['Role'] == "Administrateur") {
+ this.isAdmin = true;
+ }
+ console.log("UserID : " + this.userId);
+ if(this.userId != 0) {
+ this.bs.getPhotoUser(this.userId).subscribe((data:any) => {
+ this.createImageFromBlob(data);
+ }, error => {
+ console.log("Erreur !!");
+ this.toast.error("Erreur de récupération des infos de l'utilisateur");
+ });
+ }
+ }, error => {
+ this.toast.error("Une erreur s'est produite ...");
+ }
+ );
+ }
+
+ createImageFromBlob(image: Blob) {
+ let reader = new FileReader();
+ reader.addEventListener("load", () => {
+ this.thumbnail = reader.result;
+ }, false);
+ if(image) {
+ reader.readAsDataURL(image);
+ }
+ }
+
+ logout(): void {
+ this.bs.logoutUser().subscribe(
+ (data:any) => {
+ console.log(data);
+ this.router.navigateByUrl("/login");
+ }, error => {
+ console.log("Erreur de deconnexion de l'utilisateur");
+ });
+ }
+
+ myAccount(): void {
+ console.log("Modif user account");
+ this.evtDrv.publishEvent({type:ActionsTypes.EDIT_USER, payload:this.userId});
+ }
+
+ modifUsers(): void {
+ console.log("Modify users");
+ this.evtDrv.publishEvent({type:ActionsTypes.MODIF_USERS, payload:this.isAdmin});
+ }
+
+ messages(): void {
+ console.log("Messages");
+ this.evtDrv.publishEvent({type:ActionsTypes.SEND_MESSAGES, payload:this.isAdmin});
+ }
+}
diff --git a/src/app/components/workspace/messages/chatbox/chatbox.component.css b/src/app/components/workspace/messages/chatbox/chatbox.component.css
new file mode 100644
index 0000000..0efedec
--- /dev/null
+++ b/src/app/components/workspace/messages/chatbox/chatbox.component.css
@@ -0,0 +1,78 @@
+.imessage {
+ background-color: #fff;
+ border-radius: 0.25rem;
+ display: flex;
+ flex-direction: column;
+ margin: 0 auto 1rem;
+ padding: 0.5rem 1.5rem;
+}
+
+span {
+ position: relative;
+ font-size: 0.75rem;
+ font-style: italic;
+ display: block;
+ margin: 1rem auto 0.25rem;
+ color: #002b5c
+}
+
+p {
+ border-radius: 1.15rem;
+ line-height: 1.25;
+ max-width: 100%;
+ padding: 0.5rem .875rem;
+ position: relative;
+ word-wrap: break-word;
+ margin: 0.5rem 0;
+ width: fit-content;
+}
+
+.imessage p::before,
+.imessage p::after {
+ bottom: -0.1rem;
+ content: "";
+ height: 1rem;
+ position: absolute;
+}
+
+p.from-me {
+ align-self: end;
+ background-color: #002b5c;
+ color: #fff;
+}
+
+p.from-me::before {
+ border-bottom-left-radius: 0.8rem 0.7rem;
+ border-right: 1rem solid #002b5c;
+ right: -0.35rem;
+ transform: translate(0, -0.1rem);
+}
+
+p.from-me::after {
+ background-color: #fff;
+ border-bottom-left-radius: 0.5rem;
+ right: -40px;
+ transform:translate(-30px, -2px);
+ width: 10px;
+}
+
+p.from-them {
+ align-self: start;
+ background-color: #e5e5ea;
+ color: #000;
+}
+
+p.from-them:before {
+ border-bottom-right-radius: 0.8rem 0.7rem;
+ border-left: 1rem solid #e5e5ea;
+ left: -0.35rem;
+ transform: translate(0, -0.1rem);
+}
+
+p.from-them::after {
+ background-color: #fff;
+ border-bottom-right-radius: 0.5rem;
+ left: 20px;
+ transform: translate(-30px, -2px);
+ width: 10px;
+}
diff --git a/src/app/components/workspace/messages/chatbox/chatbox.component.html b/src/app/components/workspace/messages/chatbox/chatbox.component.html
new file mode 100644
index 0000000..395eae8
--- /dev/null
+++ b/src/app/components/workspace/messages/chatbox/chatbox.component.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+ {{msg.Text}}
+
+
+ {{msg.Text}}
+ {{msg.user?.Prenom}} {{msg.user?.Nom}}
+
+
+
+
+
diff --git a/src/app/components/workspace/messages/chatbox/chatbox.component.spec.ts b/src/app/components/workspace/messages/chatbox/chatbox.component.spec.ts
new file mode 100644
index 0000000..4555f11
--- /dev/null
+++ b/src/app/components/workspace/messages/chatbox/chatbox.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ChatboxComponent } from './chatbox.component';
+
+describe('ChatboxComponent', () => {
+ let component: ChatboxComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ ChatboxComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ChatboxComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/workspace/messages/chatbox/chatbox.component.ts b/src/app/components/workspace/messages/chatbox/chatbox.component.ts
new file mode 100644
index 0000000..8828f2d
--- /dev/null
+++ b/src/app/components/workspace/messages/chatbox/chatbox.component.ts
@@ -0,0 +1,47 @@
+import { Component, OnInit, AfterViewInit, Input, ViewChild, ContentChild, ElementRef } from '@angular/core';
+import { BackendService } from '../../../../services/backend.service';
+import { EventDriverService } from '../../../../services/event.driver.service';
+import { Message } from '../../../../model/message.model';
+import { Utilisateur } from '../../../../model/utilisateur.model';
+import { ToastrService } from 'ngx-toastr';
+import { Observable, of } from 'rxjs';
+import { catchError, map, startWith } from 'rxjs/operators';
+
+@Component({
+ selector: 'app-chatbox',
+ templateUrl: './chatbox.component.html',
+ styleUrls: ['./chatbox.component.css']
+})
+export class ChatboxComponent implements OnInit, AfterViewInit {
+ @Input() messagesInput$:Observable | null=null;
+ @ViewChild('imessage') myElmt: ElementRef;
+ utilisateur:Utilisateur;
+
+ constructor( private bs:BackendService,
+ private evtDrv:EventDriverService,
+ private toast:ToastrService ) { }
+
+ ngOnInit(): void {
+ this.bs.getCurrentUser().subscribe(
+ (data:Utilisateur) => {
+ this.utilisateur = data;
+ }, error => {
+ this.toast.error('Une erreur s\'est produite');
+ }
+ );
+ }
+
+ ngAfterViewInit(): void {
+ console.log("ngAfterViewInit()");
+ console.log(this.myElmt);
+ }
+
+ ngAfterViewChecked(): void {
+ this.myElmt.nativeElement.scrollTop = this.myElmt.nativeElement.scrollHeight;
+ console.log(this.myElmt.nativeElement.scrollTop, this.myElmt.nativeElement.scrollHeight);
+ }
+
+ onDelete(msg:Message): void {
+ console.log("Delete message : ", msg);
+ }
+}
diff --git a/src/app/components/workspace/messages/messages.component.css b/src/app/components/workspace/messages/messages.component.css
new file mode 100644
index 0000000..c4d2d0a
--- /dev/null
+++ b/src/app/components/workspace/messages/messages.component.css
@@ -0,0 +1,23 @@
+.container {
+ height: 100%;
+ width: 100%;
+ display: grid;
+}
+
+.msg-field {
+ overflow-x: hidden;
+ overflow-y: auto;
+ /*height: 500px;*/
+}
+
+.msg-box {
+ width: 100%;
+ height: 100%;
+}
+
+.mat-form-field {
+ width: 100%;
+}
+
+/*::-webkit-scrollbar { display: none; }*/
+
diff --git a/src/app/components/workspace/messages/messages.component.html b/src/app/components/workspace/messages/messages.component.html
new file mode 100644
index 0000000..87b44bb
--- /dev/null
+++ b/src/app/components/workspace/messages/messages.component.html
@@ -0,0 +1,14 @@
+
+
+
+
+ Message
+
+
+
+
+
diff --git a/src/app/components/workspace/messages/messages.component.spec.ts b/src/app/components/workspace/messages/messages.component.spec.ts
new file mode 100644
index 0000000..69163f1
--- /dev/null
+++ b/src/app/components/workspace/messages/messages.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MessagesComponent } from './messages.component';
+
+describe('MessagesComponent', () => {
+ let component: MessagesComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ MessagesComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(MessagesComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/workspace/messages/messages.component.ts b/src/app/components/workspace/messages/messages.component.ts
new file mode 100644
index 0000000..03dd9b3
--- /dev/null
+++ b/src/app/components/workspace/messages/messages.component.ts
@@ -0,0 +1,48 @@
+import { Component, OnInit } from '@angular/core';
+import { formatDate } from '@angular/common';
+import { BackendService } from '../../../services/backend.service';
+import { EventDriverService } from '../../../services/event.driver.service';
+import { Message } from '../../../model/message.model';
+import { Utilisateur } from '../../../model/utilisateur.model';
+import { ToastrService } from 'ngx-toastr';
+import { Observable } from 'rxjs';
+import { catchError, map, startWith } from 'rxjs/operators';
+
+@Component({
+ selector: 'app-messages',
+ templateUrl: './messages.component.html',
+ styleUrls: ['./messages.component.css']
+})
+export class MessagesComponent implements OnInit {
+ msgText:string = "";
+ utilisateur$:Observable|null=null;
+ messages$:Observable|null=null;
+
+ constructor( private bs:BackendService,
+ private evtDrv:EventDriverService,
+ private toast:ToastrService ) { }
+
+ ngOnInit(): void {
+ this.update();
+ }
+
+ onSend():void {
+ let msg:Message = {Text: this.msgText, Date: formatDate(new Date(), 'yyyy-MM-dd hh:mm:ss', 'en-US')};
+ this.bs.sendMessage(msg).subscribe(
+ data => {
+ this.msgText = "";
+ this.update();
+ }, error => {
+ this.toast.error("Erreur lors de l'envoi du message");
+ }
+ );
+ }
+
+ update(): void {
+ this.messages$ = this.bs.getAllMessages().pipe(
+ map(datas => {
+ return datas;
+ })
+ );
+ }
+}
diff --git a/src/app/components/workspace/user-edit/user-edit.component.css b/src/app/components/workspace/user-edit/user-edit.component.css
new file mode 100644
index 0000000..d409ea0
--- /dev/null
+++ b/src/app/components/workspace/user-edit/user-edit.component.css
@@ -0,0 +1,40 @@
+.container {
+ width: 50%;
+}
+
+mat-form-field {
+ margin-left: 1.5rem;
+ width: calc(100% - 1.5rem);
+}
+
+button {
+ padding: 5px 50px 5px 50px;
+ margin: 16px 8px 16px 8px;
+ font-size: 18px;
+ text-align: center;
+ text-decoration: none;
+ border: none;
+ outline: none;
+}
+
+mat-card {
+ width: 300px;
+}
+
+p {
+ text-align: center;
+}
+
+h1 {
+ padding: 5px 5px 5px 5px;
+ margin: 10px 10px 10px 10px;
+ text-align: center;
+ font-size: 200px;
+}
+
+h4 {
+ padding: 5px 5px 5px 5px;
+ margin: 10px 10px 10px 10px;
+ text-align: center;
+ font-size: 40px;
+}
diff --git a/src/app/components/workspace/user-edit/user-edit.component.html b/src/app/components/workspace/user-edit/user-edit.component.html
new file mode 100644
index 0000000..33a6ae7
--- /dev/null
+++ b/src/app/components/workspace/user-edit/user-edit.component.html
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
403
+
INTERDIT
+
Vous n'êtes pas autorisé à venir ici !!
+
+
diff --git a/src/app/components/workspace/user-edit/user-edit.component.spec.ts b/src/app/components/workspace/user-edit/user-edit.component.spec.ts
new file mode 100644
index 0000000..0b9e97f
--- /dev/null
+++ b/src/app/components/workspace/user-edit/user-edit.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { UserEditComponent } from './user-edit.component';
+
+describe('UserEditComponent', () => {
+ let component: UserEditComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ UserEditComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(UserEditComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/workspace/user-edit/user-edit.component.ts b/src/app/components/workspace/user-edit/user-edit.component.ts
new file mode 100644
index 0000000..0890849
--- /dev/null
+++ b/src/app/components/workspace/user-edit/user-edit.component.ts
@@ -0,0 +1,144 @@
+import { Component, OnInit, EventEmitter } from '@angular/core';
+import { FormBuilder, FormGroup, Validators, ValidatorFn, ValidationErrors, AbstractControl } from '@angular/forms';
+import { Router, ActivatedRoute } from '@angular/router';
+import { BackendService } from '../../../services/backend.service';
+import { Utilisateur } from '../../../model/utilisateur.model';
+import { EventDriverService } from '../../../services/event.driver.service';
+import { ActionsTypes, ActionEvt } from '../../../state/user.state';
+import { ToastrService } from 'ngx-toastr';
+
+@Component({
+ selector: 'app-user-edit',
+ templateUrl: './user-edit.component.html',
+ styleUrls: ['./user-edit.component.css']
+})
+export class UserEditComponent implements OnInit {
+ userFG?:FormGroup;
+ submitted:boolean=false;
+ userId:number;
+ minPw:number = 8;
+ maxPw:number = 24;
+ thumbnail:any;
+ photo:File;
+ user:Utilisateur;
+ errFlag:boolean = false;
+
+ constructor(private fb:FormBuilder,
+ private bs:BackendService,
+ private router:Router,
+ private evtDrv:EventDriverService,
+ private ar:ActivatedRoute,
+ private toast:ToastrService) {
+ this.userId=this.ar.snapshot.params['id'];
+ }
+
+ ngOnInit(): void {
+ this.bs.getUser(this.userId).subscribe(
+ (data:Utilisateur) => {
+ this.user = data;
+ this.userFG=this.fb.group({
+ nom:[this.user.Nom, Validators.required],
+ prenom:[this.user.Prenom, Validators.required],
+ identifiant:[this.user.Identifiant, Validators.required],
+ password:["", [
+ Validators.required,
+ Validators.pattern('(?=\\D*\\d)(?=[^a-z])(?=[^A-Z]*[A-Z]).{' + this.minPw + ',' + this.maxPw + '}')
+ ]],
+ confirm:["", [
+ Validators.required,
+ Validators.pattern('(?=\\D*\\d)(?=[^a-z])(?=[^A-Z]*[A-Z]).{' + this.minPw + ',' + this.maxPw + '}'),
+ passwordMatchValidator
+ ]],
+ });
+ this.bs.getPhotoUser(this.userId).subscribe(
+ (data:any) => {
+ this.createImageURL(data);
+ }, error => {
+ this.toast.error('Erreur de récupération de la photo');
+ });
+ }, error => {
+ if(error.status == 403) {
+ this.errFlag = true;
+ } else {
+ this.toast.error('Erreur de récupération des informations');
+ }
+ });
+ }
+
+ createImageURL(image:any): void {
+ let reader = new FileReader();
+ reader.addEventListener("load", () => {
+ this.thumbnail = reader.result;
+ }, false);
+ if(image) {
+ reader.readAsDataURL(image);
+ }
+ }
+
+ onImageSelected(event:any): void {
+ if(event.target.files[0]) {
+ this.photo = event.target.files[0];
+ this.createImageURL(this.photo);
+ }
+ }
+
+ onInputChange(event:any): void {
+ switch(event.target.name) {
+ case 'Prenom':
+ this.user.Prenom = event.target.value;
+ break;
+ case 'Nom':
+ this.user.Nom = event.target.value;
+ break;
+ case 'Identifiant':
+ this.user.Identifiant = event.target.value;
+ break;
+ case 'Confirm':
+ if(this.userFG?.get('password')?.value === this.userFG?.get('confirm')?.value) {
+ this.user.Password = event.target.value;
+ }
+ break;
+ default:
+ break;
+ }
+ console.log("En cours de changement ...", this.user);
+ }
+
+ onModify(): void {
+ this.submitted=true;
+ if(this.userFG?.invalid) {
+ return;
+ }
+ // DEBUG
+ console.log("Utilisateur modifié : ", this.userFG?.value);
+ // END DEBUG
+ const formData = new FormData();
+ this.bs.updateUser(this.userId, this.user).subscribe(
+ data => {
+ console.log("response : ", data);
+ // TODO: Envoyer nouvelle photo si elle existe
+ // TODO: Rafraichir les infos de la navbar
+ this.evtDrv.publishEvent({type:ActionsTypes.UPDATE_USER, payload:this.user});
+ if(this.photo) {
+ formData.append("photo", this.photo, this.photo.name);
+ this.bs.updateUserPhoto(this.userId, formData).subscribe(
+ data => {
+ this.toast.success('Mise à jour réussie');
+ }, error => {
+ this.toast.error('Erreur de mise à jour de la photo');
+ });
+ } else {
+ this.toast.success('Mise à jour réussie');
+ }
+ }, error => {
+ this.toast.error('Erreur de mise à jour des informations');
+ }
+ );
+ }
+}
+
+export const passwordMatchValidator: ValidatorFn = (fg: AbstractControl): ValidationErrors | null => {
+ let pt = fg.parent as FormGroup;
+ if(!pt) return null;
+ return pt.get('password')?.value === pt.get('confirm')?.value ? null : { 'mismatch' : true };
+}
diff --git a/src/app/components/workspace/users-list/user-item/user-item.component.css b/src/app/components/workspace/users-list/user-item/user-item.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/components/workspace/users-list/user-item/user-item.component.html b/src/app/components/workspace/users-list/user-item/user-item.component.html
new file mode 100644
index 0000000..2b95c35
--- /dev/null
+++ b/src/app/components/workspace/users-list/user-item/user-item.component.html
@@ -0,0 +1,23 @@
+
+ | {{user.Nom}} |
+ {{user.Prenom}} |
+ {{user.Identifiant}} |
+ {{user.Role}} |
+
+ check_circle
+ remove_circle
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
diff --git a/src/app/components/workspace/users-list/user-item/user-item.component.spec.ts b/src/app/components/workspace/users-list/user-item/user-item.component.spec.ts
new file mode 100644
index 0000000..afd3324
--- /dev/null
+++ b/src/app/components/workspace/users-list/user-item/user-item.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { UserItemComponent } from './user-item.component';
+
+describe('UserItemComponent', () => {
+ let component: UserItemComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ UserItemComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(UserItemComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/workspace/users-list/user-item/user-item.component.ts b/src/app/components/workspace/users-list/user-item/user-item.component.ts
new file mode 100644
index 0000000..213cb8e
--- /dev/null
+++ b/src/app/components/workspace/users-list/user-item/user-item.component.ts
@@ -0,0 +1,126 @@
+import { Component, OnInit, Input } from '@angular/core';
+import { BackendService } from '../../../../services/backend.service';
+import { Utilisateur } from '../../../../model/utilisateur.model';
+import { EventDriverService } from '../../../../services/event.driver.service';
+import { ActionsTypes, ActionEvt } from '../../../../state/user.state';
+import { ConfirmDialogComponent } from '../../../dialog/confirm-dialog/confirm-dialog.component';
+import { ToastrService } from 'ngx-toastr';
+import { MatDialog } from '@angular/material/dialog';
+
+@Component({
+ selector: 'app-user-item',
+ templateUrl: './user-item.component.html',
+ styleUrls: ['./user-item.component.css']
+})
+export class UserItemComponent implements OnInit {
+ @Input() user:Utilisateur;
+ @Input() curUser:Utilisateur;
+ isCur:boolean = false;
+ isActif:boolean = false;
+
+ constructor( private evtDrv: EventDriverService,
+ private bs: BackendService,
+ public dialog: MatDialog,
+ private toast:ToastrService) { }
+
+ ngOnInit(): void {
+ if(this.curUser.userId == this.user.userId) {
+ this.isCur = true;
+ }
+
+ this.isActif = this.user.Actif;
+ }
+
+ onResetPasswd(): void {
+ const myDialog = this.dialog.open(ConfirmDialogComponent, {
+ disableClose: true,
+ data: {
+ title: 'Ré-intilisation du mot de passe',
+ text: 'Confirmer la ré-initialisation du mot de passe pour l\'utilisateur ' + this.user.Prenom + ' ' + this.user.Nom,
+ labelOK: 'Confirmer',
+ labelNOK: 'Annuler'
+ }
+ });
+ myDialog.afterClosed().subscribe(
+ data => {
+ if(data) {
+ this.bs.resetPasswordUser(this.user.userId).subscribe(
+ (data:any) => {
+ this.toast.success("Ré-initialisation du mot de passe réussi");
+ }, error => {
+ this.toast.error("Erreur de ré-initialisation du mot de passe");
+ }
+ );
+ }
+ }, error => {
+ this.toast.error("Une erreur est survenue ...");
+ }
+ );
+ }
+
+ onActivate(user:Utilisateur): void {
+ var title:string;
+ let text:string;
+ if(user.Actif) {
+ title = 'Désactivation du compte';
+ text = 'Confirmer la désactivation de l\'utilisateur ' + user.Prenom + ' ' + user.Nom;
+
+ } else {
+ title = 'Activation du compte';
+ text = 'Confirmer l\'activation de l\'utilisateur ' + user.Prenom + ' ' + user.Nom;
+ }
+
+ const myDialog = this.dialog.open(ConfirmDialogComponent, {
+ disableClose: true,
+ data: {
+ title: title,
+ text: text,
+ labelOK: 'Confirmer',
+ labelNOK: 'Annuler'
+ }
+ });
+ myDialog.afterClosed().subscribe(
+ data => {
+ if(data) {
+ user.Actif = !user.Actif;
+ this.bs.activateUser(this.user.userId, user).subscribe(
+ (data:any) => {
+ if(user.Actif) {
+ this.toast.success("Activation de l'utilisateur réussi");
+ } else {
+ this.toast.success("Désactivation de l'utilisateur réussi");
+ }
+ }, error => {
+ this.toast.error("Erreu de désactivation de l'utilisateur");
+ }
+ );
+ }
+ }
+ );
+ }
+
+ onDelete(): void {
+ console.log("Delete User: " + this.user.userId);
+ let id = this.user?.userId;
+ const myDialog = this.dialog.open(ConfirmDialogComponent, {
+ disableClose: true,
+ data: {
+ title: 'Suppression du compte',
+ text: 'Confirmer la suppression de l\'utilisateur ' + this.user.Prenom + ' ' + this.user.Nom,
+ labelOK: 'Confirmer',
+ labelNOK: 'Annuler'
+ }
+ });
+ myDialog.afterClosed().subscribe(
+ data => {
+ console.log("réponse: ", data);
+ }
+ );
+ //this.bs.deleteUser(id).subscribe(
+ // data => {
+
+ // }, error => {
+
+ // });
+ }
+}
diff --git a/src/app/components/workspace/users-list/users-list.component.css b/src/app/components/workspace/users-list/users-list.component.css
new file mode 100644
index 0000000..9c94a55
--- /dev/null
+++ b/src/app/components/workspace/users-list/users-list.component.css
@@ -0,0 +1,23 @@
+.mat-column-Actif {
+ flex:0 0 10%;
+}
+
+.mat-column-Role {
+ flex:0 0 15%;
+}
+
+.mat-column-Nom {
+ flex:0 0 15%;
+}
+
+.mat-column-Prenom {
+ flex:0 0 15%;
+}
+
+.mat-column-Identifiant {
+ flex:0 0 15%;
+}
+
+button {
+ margin: 0.5rem;
+}
diff --git a/src/app/components/workspace/users-list/users-list.component.html b/src/app/components/workspace/users-list/users-list.component.html
new file mode 100644
index 0000000..aeece8c
--- /dev/null
+++ b/src/app/components/workspace/users-list/users-list.component.html
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+ Nom
+ {{element.Nom}}
+
+
+
+ Prenom
+ {{element.Prenom}}
+
+
+
+ Identifiant
+ {{element.Identifiant}}
+
+
+
+ Role
+ {{element.Role}}
+
+
+
+ Actif
+
+ check_circle
+ remove_circle
+
+
+
+
+ Actions
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/components/workspace/users-list/users-list.component.spec.ts b/src/app/components/workspace/users-list/users-list.component.spec.ts
new file mode 100644
index 0000000..85d9959
--- /dev/null
+++ b/src/app/components/workspace/users-list/users-list.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { UsersListComponent } from './users-list.component';
+
+describe('UsersListComponent', () => {
+ let component: UsersListComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ UsersListComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(UsersListComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/workspace/users-list/users-list.component.ts b/src/app/components/workspace/users-list/users-list.component.ts
new file mode 100644
index 0000000..27b04d3
--- /dev/null
+++ b/src/app/components/workspace/users-list/users-list.component.ts
@@ -0,0 +1,203 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import { BackendService } from '../../../services/backend.service';
+import { Utilisateur } from '../../../model/utilisateur.model';
+import { EventDriverService } from '../../../services/event.driver.service';
+import { ActionsTypes, ActionEvt } from '../../../state/user.state';
+import { ConfirmDialogComponent } from '../../dialog/confirm-dialog/confirm-dialog.component';
+import { AddUserDialogComponent } from '../../dialog/add-user-dialog/add-user-dialog.component';
+import { MatDialog } from '@angular/material/dialog';
+import { ToastrService } from 'ngx-toastr';
+import { Router } from '@angular/router';
+import { Observable, of, Subscription, Subject } from 'rxjs';
+import { catchError, map, startWith } from 'rxjs/operators';
+import { MatSort } from '@angular/material/sort';
+import { MatPaginator } from '@angular/material/paginator';
+import { MatTableDataSource } from '@angular/material/table';
+import { faTrashCan, faUserSlash, faUserCheck, faLock, faLockOpen, faUserPlus } from '@fortawesome/free-solid-svg-icons';
+
+@Component({
+ selector: 'app-users-list',
+ templateUrl: './users-list.component.html',
+ styleUrls: ['./users-list.component.css']
+})
+export class UsersListComponent implements OnInit {
+ curUser:Utilisateur;
+ isAdmin:boolean = false;
+ dataSource: MatTableDataSource = new MatTableDataSource();
+ columnsToDisplay = ['Prenom', 'Nom', 'Identifiant', 'Role', 'Actif', 'Actions'];
+ @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
+ @ViewChild(MatSort, { static: true }) sort: MatSort;
+
+ faTrashCan = faTrashCan;
+ faUserSlash = faUserSlash;
+ faUserCheck = faUserCheck;
+ faLock = faLock;
+ faLockOpen = faLockOpen;
+ faUserPlus = faUserPlus;
+
+ update:any = new Subject();
+ updateObs$ = this.update.asObservable();
+
+ constructor( private bs:BackendService,
+ private router:Router,
+ private evtDrv:EventDriverService,
+ public dialog: MatDialog,
+ private toast:ToastrService) {}
+
+ ngOnInit(): void {
+ this.bs.getCurrentUser().subscribe(
+ (data:Utilisateur) => {
+ this.curUser = data;
+ if(this.curUser['Role'] == "Administrateur") {
+ this.isAdmin = true;
+ }
+ }, error => {
+ this.toast.error("Une erreur s'est produite");
+ });
+ this.updateObs$.subscribe(() => { this.refresh() });
+ this.bs.getAllUsers().subscribe(
+ (data:Utilisateur[]) => {
+ this.dataSource.data = data;
+ this.dataSource.paginator = this.paginator;
+ this.dataSource.sort = this.sort;
+ }
+ );
+ }
+
+ setUpdate(): void {
+ this.update.next();
+ }
+
+ refresh(): void {
+ this.bs.getAllUsers().subscribe(
+ (data:Utilisateur[]) => {
+ this.dataSource.data = data;
+ this.dataSource.paginator = this.paginator;
+ this.dataSource.sort = this.sort;
+ }
+ );
+ }
+
+ onActivate(user:Utilisateur): void {
+ var title:string;
+ let text:string;
+ if(user.Actif) {
+ title = 'Désactivation du compte';
+ text = 'Confirmer la désactivation de l\'utilisateur ' + user.Prenom + ' ' + user.Nom;
+
+ } else {
+ title = 'Activation du compte';
+ text = 'Confirmer l\'activation de l\'utilisateur ' + user.Prenom + ' ' + user.Nom;
+ }
+
+ const myDialog = this.dialog.open(ConfirmDialogComponent, {
+ disableClose: true,
+ data: {
+ title: title,
+ text: text,
+ labelOK: 'Confirmer',
+ labelNOK: 'Annuler'
+ }
+ });
+
+ myDialog.afterClosed().subscribe(
+ data => {
+ if(data) {
+ user.Actif = !user.Actif;
+ this.bs.activateUser(user.userId, user).subscribe(
+ (data:any) => {
+ if(user.Actif) {
+ this.toast.success("Activation de l'utilisateur réussi");
+ } else {
+ this.toast.success("Désactivation de l'utilisateur réussi");
+ }
+ }, error => {
+ this.toast.error("Erreur de désactivation de l'utilisateur");
+ }
+ );
+ }
+ }
+ );
+ }
+
+ onDelete(user:Utilisateur): void {
+ const myDialog = this.dialog.open(ConfirmDialogComponent, {
+ disableClose: true,
+ data: {
+ title: 'Suppression de l\'utilisateur',
+ text: 'Confirmer la suppression de l\'utilisateur ' + user.Prenom + ' ' + user.Nom,
+ labelOK: 'Confirmer',
+ labelNOK: 'Annuler'
+ }
+ });
+
+ myDialog.afterClosed().subscribe(
+ data => {
+ if(data) {
+ this.bs.deleteUser(user.userId).subscribe(
+ (data:any) => {
+ this.toast.success("Suppression de l'utilisateur réussi");
+ this.setUpdate();
+ }, error => {
+ this.toast.error("Erreur de suppression de l'utilisateur");
+ }
+ );
+ }
+ }
+ );
+ }
+
+ onResetPasswd(user:Utilisateur): void {
+ const myDialog = this.dialog.open(ConfirmDialogComponent, {
+ disableClose: true,
+ data: {
+ title: 'Ré-initialisation du mot de passe',
+ text: 'Confirmer la ré-initialisation du mot de passe pour l\'utilisateur ' + user.Prenom + ' ' + user.Nom,
+ labelOK: 'Confirmer',
+ labelNOK: 'Annuler'
+ }
+ });
+ myDialog.afterClosed().subscribe(
+ data => {
+ if(data) {
+ this.bs.resetPasswordUser(user.userId).subscribe(
+ (data:any) => {
+ this.toast.success("Ré-initialisation du mot de passe réussi");
+ }, error => {
+ this.toast.error("Erreur de ré-initialisation du mot de passe");
+ }
+ );
+ }
+ }, error => {
+ this.toast.error("Une erreur est survenue ...");
+ }
+ );
+ }
+
+ onAddUser(): void {
+ let user:Utilisateur = {userId:0, Nom:'', Prenom:'', Role:'', Identifiant:'', Password:'', Actif:false, Photo:''};
+ const myDialog = this.dialog.open(AddUserDialogComponent, {
+ disableClose: true,
+ width: '400px'
+ });
+ myDialog.afterClosed().subscribe(
+ data => {
+ if(data) {
+ user.Prenom = data['prenom'];
+ user.Nom = data['nom'];
+ user.Identifiant = data['identifiant'];
+ user.Role = data['role'];
+ user.Actif = true;
+ this.bs.saveUser(user).subscribe(
+ data => {
+ this.toast.success("Enregistrement de l'utilisateur réussi");
+ this.setUpdate();
+ }, error => {
+ this.toast.error("Erreur lors de l'enregistrement de l'utilisateur");
+ }
+ );
+ }
+ }
+ );
+ }
+}
diff --git a/src/app/components/workspace/workspace.component.css b/src/app/components/workspace/workspace.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/components/workspace/workspace.component.html b/src/app/components/workspace/workspace.component.html
new file mode 100644
index 0000000..0680b43
--- /dev/null
+++ b/src/app/components/workspace/workspace.component.html
@@ -0,0 +1 @@
+
diff --git a/src/app/components/workspace/workspace.component.spec.ts b/src/app/components/workspace/workspace.component.spec.ts
new file mode 100644
index 0000000..c0c0c01
--- /dev/null
+++ b/src/app/components/workspace/workspace.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { WorkspaceComponent } from './workspace.component';
+
+describe('WorkspaceComponent', () => {
+ let component: WorkspaceComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ WorkspaceComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(WorkspaceComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/workspace/workspace.component.ts b/src/app/components/workspace/workspace.component.ts
new file mode 100644
index 0000000..aeeb5e3
--- /dev/null
+++ b/src/app/components/workspace/workspace.component.ts
@@ -0,0 +1,59 @@
+import { Component, OnInit } from '@angular/core';
+import { Utilisateur } from '../../model/utilisateur.model';
+import { ActionsTypes, ActionEvt } from '../../state/user.state';
+import { EventDriverService } from '../../services/event.driver.service';
+import { Observable, of, Subscription } from 'rxjs';
+import { Router } from '@angular/router';
+
+@Component({
+ selector: 'app-workspace',
+ templateUrl: './workspace.component.html',
+ styleUrls: ['./workspace.component.css']
+})
+export class WorkspaceComponent implements OnInit {
+ sub:Subscription;
+
+ constructor( private evtDrv:EventDriverService, private router:Router ) { }
+
+ ngOnInit(): void {
+ this.sub=this.evtDrv.sourceEventSubjectObservable.subscribe(
+ (data:ActionEvt) => {
+ this.onActionEvt(data);
+ });
+ }
+
+ onActionEvt($event: ActionEvt): void {
+ switch($event.type) {
+ case ActionsTypes.EDIT_USER: {
+ this.onEditUser($event.payload);
+ break;
+ }
+
+ case ActionsTypes.MODIF_USERS: {
+ this.onListUsers();
+ break;
+ }
+
+ case ActionsTypes.SEND_MESSAGES: {
+ this.onMessages();
+ break;
+ }
+
+ default: {
+ break;
+ }
+ }
+ }
+
+ onEditUser(val:number): void {
+ this.router.navigateByUrl("/edit/"+val);
+ }
+
+ onListUsers(): void {
+ this.router.navigateByUrl("/utilisateurs");
+ }
+
+ onMessages(): void {
+ this.router.navigateByUrl("/messages");
+ }
+}
diff --git a/src/app/interceptors/http-xsrf-interceptor/http-xsrf-interceptor.service.spec.ts b/src/app/interceptors/http-xsrf-interceptor/http-xsrf-interceptor.service.spec.ts
new file mode 100644
index 0000000..9c78b2c
--- /dev/null
+++ b/src/app/interceptors/http-xsrf-interceptor/http-xsrf-interceptor.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { HttpXsrfInterceptorService } from './http-xsrf-interceptor.service';
+
+describe('HttpXsrfInterceptorService', () => {
+ let service: HttpXsrfInterceptorService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(HttpXsrfInterceptorService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/src/app/interceptors/http-xsrf-interceptor/http-xsrf-interceptor.service.ts b/src/app/interceptors/http-xsrf-interceptor/http-xsrf-interceptor.service.ts
new file mode 100644
index 0000000..371d0bb
--- /dev/null
+++ b/src/app/interceptors/http-xsrf-interceptor/http-xsrf-interceptor.service.ts
@@ -0,0 +1,28 @@
+import { Injectable } from '@angular/core';
+import { HttpRequest, HttpHandler, HttpEvent, HttpXsrfTokenExtractor } from '@angular/common/http';
+import { Observable } from 'rxjs';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class HttpXsrfInterceptorService {
+ constructor(private tokenExtractor: HttpXsrfTokenExtractor) { }
+
+ intercept(req: HttpRequest, next: HttpHandler): Observable> {
+ // DEBUG
+ console.log("[Interceptor]", req);
+ // END DEBUG
+ const headerName = 'X-CSRF-TOKEN';
+ const token = this.tokenExtractor.getToken() as string;
+ // DEBUG
+ console.log('[Interceptor] token : ' + token);
+ // END DEBUG
+
+ if (token != null && !req.headers.has(headerName)) {
+ req = req.clone({
+ headers: req.headers.set(headerName, token)
+ });
+ }
+ return next.handle(req);
+ }
+}
diff --git a/src/app/model/message.model.ts b/src/app/model/message.model.ts
new file mode 100644
index 0000000..cbb6dd4
--- /dev/null
+++ b/src/app/model/message.model.ts
@@ -0,0 +1,12 @@
+export interface User {
+ Prenom:string;
+ Nom:string;
+ userId:number;
+}
+
+export interface Message {
+ msgId?:number;
+ Text:string;
+ Date:string;
+ user?:User;
+}
diff --git a/src/app/model/utilisateur.model.ts b/src/app/model/utilisateur.model.ts
new file mode 100644
index 0000000..eae792f
--- /dev/null
+++ b/src/app/model/utilisateur.model.ts
@@ -0,0 +1,10 @@
+export interface Utilisateur {
+ userId:number;
+ Nom:string;
+ Prenom:string;
+ Role:string;
+ Identifiant:string;
+ Password:string;
+ Actif:boolean;
+ Photo:string;
+}
diff --git a/src/app/services/backend.service.ts b/src/app/services/backend.service.ts
new file mode 100644
index 0000000..26f24e7
--- /dev/null
+++ b/src/app/services/backend.service.ts
@@ -0,0 +1,155 @@
+import { Injectable } from '@angular/core';
+import { HttpClient, HttpHeaders } from '@angular/common/http';
+import { environment } from '../../environments/environment';
+import { Utilisateur } from '../model/utilisateur.model';
+import { Message } from '../model/message.model';
+import { Observable } from 'rxjs';
+
+@Injectable({providedIn:"root"})
+export class BackendService {
+ constructor(private http:HttpClient) {}
+
+ getAllUsers():Observable {
+ let host=environment.host;
+ const options = {
+ headers: new HttpHeaders({
+ 'Content-Type' : 'application/json',
+ }),
+ withCredentials: true
+ };
+ return this.http.get(host+"/api/utilisateurs", options);
+ }
+
+ deleteUser(id:number):Observable {
+ let host=environment.host;
+ const options = {
+ headers: new HttpHeaders({
+ 'Content-Type' : 'application/json',
+ }),
+ withCredentials: true
+ };
+ return this.http.delete(host+"/api/utilisateurs/" + id, options);
+ }
+
+ saveUser(val:Utilisateur):Observable {
+ let host=environment.host;
+ const options = {
+ headers: new HttpHeaders({
+ 'Content-Type' : 'application/json',
+ }),
+ withCredentials: true
+ };
+ return this.http.post(host+"/api/utilisateurs", val, options);
+ }
+
+ getUser(id:number):Observable {
+ let host=environment.host;
+ const options = {
+ headers: new HttpHeaders({
+ 'Content-Type' : 'application/json',
+ }),
+ withCredentials: true
+ };
+ return this.http.get(host+"/api/utilisateurs/" + id, options);
+ }
+
+ updateUser(id:number, val:Utilisateur):Observable {
+ let host=environment.host;
+ const options = {
+ headers: new HttpHeaders({
+ 'Content-Type' : 'application/json',
+ }),
+ withCredentials: true
+ };
+ return this.http.put(host+"/api/utilisateurs/"+id, val, options);
+ }
+
+ updateUserPhoto(id:number, val:FormData):Observable {
+ let host=environment.host;
+ return this.http.post(host+"/api/utilisateurs/" + id + "/uploadImage", val, { withCredentials: true });
+ }
+
+ getPhotoUser(id:number):Observable {
+ let host=environment.host;
+ return this.http.get(host+"/api/utilisateurs/" + id + "/photo", { withCredentials: true, responseType: 'blob'});
+ }
+
+ getCurrentUser():Observable {
+ let host=environment.host;
+ const options = {
+ headers: new HttpHeaders({
+ 'Content-Type' : 'application/json',
+ }),
+ withCredentials: true
+ };
+ return this.http.get(host+"/api/utilisateurs/current", options);
+ }
+
+ resetPasswordUser(id:number):Observable {
+ let host=environment.host;
+ const options = {
+ headers: new HttpHeaders({
+ 'Content-Type' : 'application/json',
+ }),
+ withCredentials: true
+ };
+ return this.http.get(host+"/api/utilisateurs/" + id + "/reset_password", options);
+ }
+
+ activateUser(id:number, val:Utilisateur):Observable {
+ let host=environment.host;
+ const options = {
+ headers: new HttpHeaders({
+ 'Content-Type' : 'application/json',
+ }),
+ withCredentials: true
+ };
+ return this.http.put(host+"/api/utilisateurs/" + id + "/activate", val, options);
+ }
+
+ loginUser(val:any) {
+ let host=environment.host;
+ const options = {
+ headers: new HttpHeaders({
+ 'Content-Type' : 'application/json',
+ 'Authorization' : 'Basic ' + btoa(val['login'] + ':' + val['password']),
+ }),
+ withCredentials: true
+ };
+
+ return this.http.post(host+"/api/utilisateurs/login", {}, options);
+ }
+
+ logoutUser():Observable {
+ let host=environment.host;
+ const options = {
+ headers: new HttpHeaders({
+ 'Content-Type' : 'application/json',
+ }),
+ withCredentials: true
+ };
+ return this.http.post(host+"/api/utilisateurs/logout", {}, options);
+ }
+
+ getAllMessages():Observable {
+ let host=environment.host;
+ const options = {
+ headers: new HttpHeaders({
+ 'Content-Type' : 'application/json',
+ }),
+ withCredentials: true
+ };
+ return this.http.get(host+"/api/messages", options);
+ }
+
+ sendMessage(val:Message):Observable {
+ let host=environment.host;
+ const options = {
+ headers: new HttpHeaders({
+ 'Content-Type' : 'application/json',
+ }),
+ withCredentials: true
+ };
+ return this.http.post(host+"/api/messages", val, options);
+ }
+}
diff --git a/src/app/services/event.driver.service.ts b/src/app/services/event.driver.service.ts
new file mode 100644
index 0000000..2c66edc
--- /dev/null
+++ b/src/app/services/event.driver.service.ts
@@ -0,0 +1,15 @@
+import { Injectable } from '@angular/core';
+import { Subject } from 'rxjs';
+import { ActionEvt } from '../state/user.state';
+
+@Injectable({
+ providedIn:"root"
+})
+export class EventDriverService {
+ sourceEventSubject:Subject=new Subject();
+ sourceEventSubjectObservable=this.sourceEventSubject.asObservable();
+
+ publishEvent(event:ActionEvt) {
+ this.sourceEventSubject.next(event);
+ }
+}
diff --git a/src/app/services/profile/profile.service.spec.ts b/src/app/services/profile/profile.service.spec.ts
new file mode 100644
index 0000000..2ddf7f2
--- /dev/null
+++ b/src/app/services/profile/profile.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { ProfileService } from './profile.service';
+
+describe('ProfileService', () => {
+ let service: ProfileService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(ProfileService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/src/app/services/profile/profile.service.ts b/src/app/services/profile/profile.service.ts
new file mode 100644
index 0000000..f8cbfe7
--- /dev/null
+++ b/src/app/services/profile/profile.service.ts
@@ -0,0 +1,46 @@
+import { Injectable } from '@angular/core';
+import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
+import { Observable } from 'rxjs';
+import { BackendService } from '../backend.service';
+import { Utilisateur } from '../../model/utilisateur.model';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class ProfileService implements CanActivate {
+ profile:Utilisateur;
+
+ constructor( private bs:BackendService, private router:Router ) { }
+
+ getProfile(): Observable {
+ return new Observable((observer) => {
+ if(this.profile) {
+ console.log("Profile non null");
+ observer.next(this.profile);
+ observer.complete();
+ } else {
+ this.bs.getCurrentUser().subscribe(profile => {
+ this.profile = profile;
+ observer.next(profile);
+ observer.complete()
+ }, error => {
+ observer.error(error);
+ observer.complete();
+ })
+ }
+ });
+ }
+
+ canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable {
+ return new Observable((observer) => {
+ this.getProfile().subscribe(profile => {
+ observer.next(true);
+ observer.complete();
+ }, error => {
+ this.router.navigateByUrl("/login");
+ observer.next(false);
+ observer.complete();
+ })
+ });
+ }
+}
diff --git a/src/app/state/user.state.ts b/src/app/state/user.state.ts
new file mode 100644
index 0000000..04693ac
--- /dev/null
+++ b/src/app/state/user.state.ts
@@ -0,0 +1,24 @@
+export enum ActionsTypes {
+ EDIT_USER="[Utilisateur] Editer utilisateur",
+ UPDATE_USER="[Utilisateur] Mise à jour des infos de l'utiliateur",
+ MODIF_USERS="[Utilisateurs] Modification des utilisateurs",
+ SEND_MESSAGES="[Utilisateurs] Envoie de messages"
+}
+
+export interface ActionEvt {
+ type:ActionsTypes,
+ payload?:any,
+ errMsg?:string
+}
+
+export enum DataStateEnum {
+ LOADING,
+ LOADED,
+ ERROR
+}
+
+export interface AppDataState {
+ dataState?:DataStateEnum,
+ data?:T,
+ errorMessage?:string
+}
diff --git a/src/environments/environment.ts b/src/environments/environment.ts
index f56ff47..d67b1d7 100644
--- a/src/environments/environment.ts
+++ b/src/environments/environment.ts
@@ -3,7 +3,8 @@
// The list of file replacements can be found in `angular.json`.
export const environment = {
- production: false
+ production: false,
+ host: "http://localhost:5000"
};
/*
diff --git a/src/index.html b/src/index.html
index 715e912..4de8fae 100644
--- a/src/index.html
+++ b/src/index.html
@@ -6,6 +6,9 @@
+
+
+
diff --git a/src/styles.css b/src/styles.css
index 90d4ee0..7fa6b49 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -1 +1,5 @@
/* You can add global styles to this file, and also import other style files */
+@import "~font-awesome/css/font-awesome.min.css";
+
+html, body { height: 100%; }
+body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
diff --git a/tsconfig.json b/tsconfig.json
index f531992..a27269a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -10,6 +10,7 @@
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
+ "strictPropertyInitialization": false,
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
@@ -20,7 +21,8 @@
"module": "es2020",
"lib": [
"es2020",
- "dom"
+ "dom",
+ "dom.iterable"
]
},
"angularCompilerOptions": {