ajout des sources

This commit is contained in:
2022-06-20 15:55:00 +02:00
parent 3c61cb220a
commit 26d1fed49d
63 changed files with 2833 additions and 499 deletions

View File

@@ -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": []

285
package-lock.json generated
View File

@@ -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",

View File

@@ -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"

View File

@@ -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 { }

View File

@@ -1,484 +1 @@
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * * The content below * * * * * * * * * * * -->
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * Delete the template below * * * * * * * * * * -->
<!-- * * * * * * * to get started with your project! * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<style>
:host {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 14px;
color: #333;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 8px 0;
}
p {
margin: 0;
}
.spacer {
flex: 1;
}
.toolbar {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 60px;
display: flex;
align-items: center;
background-color: #1976d2;
color: white;
font-weight: 600;
}
.toolbar img {
margin: 0 16px;
}
.toolbar #twitter-logo {
height: 40px;
margin: 0 8px;
}
.toolbar #youtube-logo {
height: 40px;
margin: 0 16px;
}
.toolbar #twitter-logo:hover,
.toolbar #youtube-logo:hover {
opacity: 0.8;
}
.content {
display: flex;
margin: 82px auto 32px;
padding: 0 16px;
max-width: 960px;
flex-direction: column;
align-items: center;
}
svg.material-icons {
height: 24px;
width: auto;
}
svg.material-icons:not(:last-child) {
margin-right: 8px;
}
.card svg.material-icons path {
fill: #888;
}
.card-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin-top: 16px;
}
.card {
all: unset;
border-radius: 4px;
border: 1px solid #eee;
background-color: #fafafa;
height: 40px;
width: 200px;
margin: 0 8px 16px;
padding: 16px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
transition: all 0.2s ease-in-out;
line-height: 24px;
}
.card-container .card:not(:last-child) {
margin-right: 0;
}
.card.card-small {
height: 16px;
width: 168px;
}
.card-container .card:not(.highlight-card) {
cursor: pointer;
}
.card-container .card:not(.highlight-card):hover {
transform: translateY(-3px);
box-shadow: 0 4px 17px rgba(0, 0, 0, 0.35);
}
.card-container .card:not(.highlight-card):hover .material-icons path {
fill: rgb(105, 103, 103);
}
.card.highlight-card {
background-color: #1976d2;
color: white;
font-weight: 600;
border: none;
width: auto;
min-width: 30%;
position: relative;
}
.card.card.highlight-card span {
margin-left: 60px;
}
svg#rocket {
width: 80px;
position: absolute;
left: -10px;
top: -24px;
}
svg#rocket-smoke {
height: calc(100vh - 95px);
position: absolute;
top: 10px;
right: 180px;
z-index: -10;
}
a,
a:visited,
a:hover {
color: #1976d2;
text-decoration: none;
}
a:hover {
color: #125699;
}
.terminal {
position: relative;
width: 80%;
max-width: 600px;
border-radius: 6px;
padding-top: 45px;
margin-top: 8px;
overflow: hidden;
background-color: rgb(15, 15, 16);
}
.terminal::before {
content: "\2022 \2022 \2022";
position: absolute;
top: 0;
left: 0;
height: 4px;
background: rgb(58, 58, 58);
color: #c2c3c4;
width: 100%;
font-size: 2rem;
line-height: 0;
padding: 14px 0;
text-indent: 4px;
}
.terminal pre {
font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;
color: white;
padding: 0 1rem 1rem;
margin: 0;
}
.circle-link {
height: 40px;
width: 40px;
border-radius: 40px;
margin: 8px;
background-color: white;
border: 1px solid #eeeeee;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
transition: 1s ease-out;
}
.circle-link:hover {
transform: translateY(-0.25rem);
box-shadow: 0px 3px 15px rgba(0, 0, 0, 0.2);
}
footer {
margin-top: 8px;
display: flex;
align-items: center;
line-height: 20px;
}
footer a {
display: flex;
align-items: center;
}
.github-star-badge {
color: #24292e;
display: flex;
align-items: center;
font-size: 12px;
padding: 3px 10px;
border: 1px solid rgba(27,31,35,.2);
border-radius: 3px;
background-image: linear-gradient(-180deg,#fafbfc,#eff3f6 90%);
margin-left: 4px;
font-weight: 600;
}
.github-star-badge:hover {
background-image: linear-gradient(-180deg,#f0f3f6,#e6ebf1 90%);
border-color: rgba(27,31,35,.35);
background-position: -.5em;
}
.github-star-badge .material-icons {
height: 16px;
width: 16px;
margin-right: 4px;
}
svg#clouds {
position: fixed;
bottom: -160px;
left: -230px;
z-index: -10;
width: 1920px;
}
/* Responsive Styles */
@media screen and (max-width: 767px) {
.card-container > *:not(.circle-link) ,
.terminal {
width: 100%;
}
.card:not(.highlight-card) {
height: 16px;
margin: 8px 0;
}
.card.highlight-card span {
margin-left: 72px;
}
svg#rocket-smoke {
right: 120px;
transform: rotate(-5deg);
}
}
@media screen and (max-width: 575px) {
svg#rocket-smoke {
display: none;
visibility: hidden;
}
}
</style>
<!-- Toolbar -->
<div class="toolbar" role="banner">
<img
width="40"
alt="Angular Logo"
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg=="
/>
<span>Welcome</span>
<div class="spacer"></div>
<a aria-label="Angular on twitter" target="_blank" rel="noopener" href="https://twitter.com/angular" title="Twitter">
<svg id="twitter-logo" height="24" data-name="Logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400">
<rect width="400" height="400" fill="none"/>
<path d="M153.62,301.59c94.34,0,145.94-78.16,145.94-145.94,0-2.22,0-4.43-.15-6.63A104.36,104.36,0,0,0,325,122.47a102.38,102.38,0,0,1-29.46,8.07,51.47,51.47,0,0,0,22.55-28.37,102.79,102.79,0,0,1-32.57,12.45,51.34,51.34,0,0,0-87.41,46.78A145.62,145.62,0,0,1,92.4,107.81a51.33,51.33,0,0,0,15.88,68.47A50.91,50.91,0,0,1,85,169.86c0,.21,0,.43,0,.65a51.31,51.31,0,0,0,41.15,50.28,51.21,51.21,0,0,1-23.16.88,51.35,51.35,0,0,0,47.92,35.62,102.92,102.92,0,0,1-63.7,22A104.41,104.41,0,0,1,75,278.55a145.21,145.21,0,0,0,78.62,23" fill="#fff"/>
</svg>
</a>
<a aria-label="Angular on YouTube" target="_blank" rel="noopener" href="https://youtube.com/angular" title="YouTube">
<svg id="youtube-logo" height="24" width="24" data-name="Logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#fff">
<path d="M0 0h24v24H0V0z" fill="none"/>
<path d="M21.58 7.19c-.23-.86-.91-1.54-1.77-1.77C18.25 5 12 5 12 5s-6.25 0-7.81.42c-.86.23-1.54.91-1.77 1.77C2 8.75 2 12 2 12s0 3.25.42 4.81c.23.86.91 1.54 1.77 1.77C5.75 19 12 19 12 19s6.25 0 7.81-.42c.86-.23 1.54-.91 1.77-1.77C22 15.25 22 12 22 12s0-3.25-.42-4.81zM10 15V9l5.2 3-5.2 3z"/>
</svg>
</a>
</div>
<div class="content" role="main">
<!-- Highlight Card -->
<div class="card highlight-card card-small">
<svg id="rocket" xmlns="http://www.w3.org/2000/svg" width="101.678" height="101.678" viewBox="0 0 101.678 101.678">
<title>Rocket Ship</title>
<g id="Group_83" data-name="Group 83" transform="translate(-141 -696)">
<circle id="Ellipse_8" data-name="Ellipse 8" cx="50.839" cy="50.839" r="50.839" transform="translate(141 696)" fill="#dd0031"/>
<g id="Group_47" data-name="Group 47" transform="translate(165.185 720.185)">
<path id="Path_33" data-name="Path 33" d="M3.4,42.615a3.084,3.084,0,0,0,3.553,3.553,21.419,21.419,0,0,0,12.215-6.107L9.511,30.4A21.419,21.419,0,0,0,3.4,42.615Z" transform="translate(0.371 3.363)" fill="#fff"/>
<path id="Path_34" data-name="Path 34" d="M53.3,3.221A3.09,3.09,0,0,0,50.081,0,48.227,48.227,0,0,0,18.322,13.437c-6-1.666-14.991-1.221-18.322,7.218A33.892,33.892,0,0,1,9.439,25.1l-.333.666a3.013,3.013,0,0,0,.555,3.553L23.985,43.641a2.9,2.9,0,0,0,3.553.555l.666-.333A33.892,33.892,0,0,1,32.647,53.3c8.55-3.664,8.884-12.326,7.218-18.322A48.227,48.227,0,0,0,53.3,3.221ZM34.424,9.772a6.439,6.439,0,1,1,9.106,9.106,6.368,6.368,0,0,1-9.106,0A6.467,6.467,0,0,1,34.424,9.772Z" transform="translate(0 0.005)" fill="#fff"/>
</g>
</g>
</svg>
<span>{{ title }} app is running!</span>
<svg id="rocket-smoke" xmlns="http://www.w3.org/2000/svg" width="516.119" height="1083.632" viewBox="0 0 516.119 1083.632">
<title>Rocket Ship Smoke</title>
<path id="Path_40" data-name="Path 40" d="M644.6,141S143.02,215.537,147.049,870.207s342.774,201.755,342.774,201.755S404.659,847.213,388.815,762.2c-27.116-145.51-11.551-384.124,271.9-609.1C671.15,139.365,644.6,141,644.6,141Z" transform="translate(-147.025 -140.939)" fill="#f5f5f5"/>
</svg>
</div>
<!-- Resources -->
<h2>Resources</h2>
<p>Here are some links to help you get started:</p>
<div class="card-container">
<a class="card" target="_blank" rel="noopener" href="https://angular.io/tutorial">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M5 13.18v4L12 21l7-3.82v-4L12 17l-7-3.82zM12 3L1 9l11 6 9-4.91V17h2V9L12 3z"/></svg>
<span>Learn Angular</span>
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg> </a>
<a class="card" target="_blank" rel="noopener" href="https://angular.io/cli">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/></svg>
<span>CLI Documentation</span>
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
</a>
<a class="card" target="_blank" rel="noopener" href="https://material.angular.io">
<svg xmlns="http://www.w3.org/2000/svg" style="margin-right: 8px" width="21.813" height="23.453" viewBox="0 0 21.813 23.453"><path d="M4099.584,972.736h0l-10.882,3.9,1.637,14.4,9.245,5.153,9.245-5.153,1.686-14.4Z" transform="translate(-4088.702 -972.736)" fill="#808080"/><path d="M4181.516,972.736v23.453l9.245-5.153,1.686-14.4Z" transform="translate(-4170.633 -972.736)" fill="#808080"/><path d="M4137.529,1076.127l-7.7-3.723,4.417-2.721,7.753,3.723Z" transform="translate(-4125.003 -1058.315)" fill="#ffe0b2"/><path d="M4137.529,1051.705l-7.7-3.723,4.417-2.721,7.753,3.723Z" transform="translate(-4125.003 -1036.757)" fill="#fff3e0"/><path d="M4137.529,1027.283l-7.7-3.723,4.417-2.721,7.753,3.723Z" transform="translate(-4125.003 -1015.199)" fill="#fff"/></svg>
<span>Angular Material</span>
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
</a>
<a class="card" target="_blank" rel="noopener" href="https://blog.angular.io/">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M13.5.67s.74 2.65.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l.03-.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8C20 8.61 17.41 3.8 13.5.67zM11.71 19c-1.78 0-3.22-1.4-3.22-3.14 0-1.62 1.05-2.76 2.81-3.12 1.77-.36 3.6-1.21 4.62-2.58.39 1.29.59 2.65.59 4.04 0 2.65-2.15 4.8-4.8 4.8z"/></svg>
<span>Angular Blog</span>
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
</a>
<a class="card" target="_blank" rel="noopener" href="https://angular.io/devtools/">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><g><path d="M14.73,13.31C15.52,12.24,16,10.93,16,9.5C16,5.91,13.09,3,9.5,3S3,5.91,3,9.5C3,13.09,5.91,16,9.5,16 c1.43,0,2.74-0.48,3.81-1.27L19.59,21L21,19.59L14.73,13.31z M9.5,14C7.01,14,5,11.99,5,9.5S7.01,5,9.5,5S14,7.01,14,9.5 S11.99,14,9.5,14z"/><polygon points="10.29,8.44 9.5,6 8.71,8.44 6.25,8.44 8.26,10.03 7.49,12.5 9.5,10.97 11.51,12.5 10.74,10.03 12.75,8.44"/></g></g></svg>
<span>Angular DevTools</span>
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
</a>
</div>
<!-- Next Steps -->
<h2>Next Steps</h2>
<p>What do you want to do next with your app?</p>
<input type="hidden" #selection>
<div class="card-container">
<button class="card card-small" (click)="selection.value = 'component'" tabindex="0">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
<span>New Component</span>
</button>
<button class="card card-small" (click)="selection.value = 'material'" tabindex="0">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
<span>Angular Material</span>
</button>
<button class="card card-small" (click)="selection.value = 'pwa'" tabindex="0">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
<span>Add PWA Support</span>
</button>
<button class="card card-small" (click)="selection.value = 'dependency'" tabindex="0">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
<span>Add Dependency</span>
</button>
<button class="card card-small" (click)="selection.value = 'test'" tabindex="0">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
<span>Run and Watch Tests</span>
</button>
<button class="card card-small" (click)="selection.value = 'build'" tabindex="0">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
<span>Build for Production</span>
</button>
</div>
<!-- Terminal -->
<div class="terminal" [ngSwitch]="selection.value">
<pre *ngSwitchDefault>ng generate component xyz</pre>
<pre *ngSwitchCase="'material'">ng add @angular/material</pre>
<pre *ngSwitchCase="'pwa'">ng add @angular/pwa</pre>
<pre *ngSwitchCase="'dependency'">ng add _____</pre>
<pre *ngSwitchCase="'test'">ng test</pre>
<pre *ngSwitchCase="'build'">ng build</pre>
</div>
<!-- Links -->
<div class="card-container">
<a class="circle-link" title="Find a Local Meetup" href="https://www.meetup.com/find/?keywords=angular" target="_blank" rel="noopener">
<svg xmlns="http://www.w3.org/2000/svg" width="24.607" height="23.447" viewBox="0 0 24.607 23.447">
<title>Meetup Logo</title>
<path id="logo--mSwarm" d="M21.221,14.95A4.393,4.393,0,0,1,17.6,19.281a4.452,4.452,0,0,1-.8.069c-.09,0-.125.035-.154.117a2.939,2.939,0,0,1-2.506,2.091,2.868,2.868,0,0,1-2.248-.624.168.168,0,0,0-.245-.005,3.926,3.926,0,0,1-2.589.741,4.015,4.015,0,0,1-3.7-3.347,2.7,2.7,0,0,1-.043-.38c0-.106-.042-.146-.143-.166a3.524,3.524,0,0,1-1.516-.69A3.623,3.623,0,0,1,2.23,14.557a3.66,3.66,0,0,1,1.077-3.085.138.138,0,0,0,.026-.2,3.348,3.348,0,0,1-.451-1.821,3.46,3.46,0,0,1,2.749-3.28.44.44,0,0,0,.355-.281,5.072,5.072,0,0,1,3.863-3,5.028,5.028,0,0,1,3.555.666.31.31,0,0,0,.271.03A4.5,4.5,0,0,1,18.3,4.7a4.4,4.4,0,0,1,1.334,2.751,3.658,3.658,0,0,1,.022.706.131.131,0,0,0,.1.157,2.432,2.432,0,0,1,1.574,1.645,2.464,2.464,0,0,1-.7,2.616c-.065.064-.051.1-.014.166A4.321,4.321,0,0,1,21.221,14.95ZM13.4,14.607a2.09,2.09,0,0,0,1.409,1.982,4.7,4.7,0,0,0,1.275.221,1.807,1.807,0,0,0,.9-.151.542.542,0,0,0,.321-.545.558.558,0,0,0-.359-.534,1.2,1.2,0,0,0-.254-.078c-.262-.047-.526-.086-.787-.138a.674.674,0,0,1-.617-.75,3.394,3.394,0,0,1,.218-1.109c.217-.658.509-1.286.79-1.918a15.609,15.609,0,0,0,.745-1.86,1.95,1.95,0,0,0,.06-1.073,1.286,1.286,0,0,0-1.051-1.033,1.977,1.977,0,0,0-1.521.2.339.339,0,0,1-.446-.042c-.1-.092-.2-.189-.307-.284a1.214,1.214,0,0,0-1.643-.061,7.563,7.563,0,0,1-.614.512A.588.588,0,0,1,10.883,8c-.215-.115-.437-.215-.659-.316a2.153,2.153,0,0,0-.695-.248A2.091,2.091,0,0,0,7.541,8.562a9.915,9.915,0,0,0-.405.986c-.559,1.545-1.015,3.123-1.487,4.7a1.528,1.528,0,0,0,.634,1.777,1.755,1.755,0,0,0,1.5.211,1.35,1.35,0,0,0,.824-.858c.543-1.281,1.032-2.584,1.55-3.875.142-.355.28-.712.432-1.064a.548.548,0,0,1,.851-.24.622.622,0,0,1,.185.539,2.161,2.161,0,0,1-.181.621c-.337.852-.68,1.7-1.018,2.552a2.564,2.564,0,0,0-.173.528.624.624,0,0,0,.333.71,1.073,1.073,0,0,0,.814.034,1.22,1.22,0,0,0,.657-.655q.758-1.488,1.511-2.978.35-.687.709-1.37a1.073,1.073,0,0,1,.357-.434.43.43,0,0,1,.463-.016.373.373,0,0,1,.153.387.7.7,0,0,1-.057.236c-.065.157-.127.316-.2.469-.42.883-.846,1.763-1.262,2.648A2.463,2.463,0,0,0,13.4,14.607Zm5.888,6.508a1.09,1.09,0,0,0-2.179.006,1.09,1.09,0,0,0,2.179-.006ZM1.028,12.139a1.038,1.038,0,1,0,.01-2.075,1.038,1.038,0,0,0-.01,2.075ZM13.782.528a1.027,1.027,0,1,0-.011,2.055A1.027,1.027,0,0,0,13.782.528ZM22.21,6.95a.882.882,0,0,0-1.763.011A.882.882,0,0,0,22.21,6.95ZM4.153,4.439a.785.785,0,1,0,.787-.78A.766.766,0,0,0,4.153,4.439Zm8.221,18.22a.676.676,0,1,0-.677.666A.671.671,0,0,0,12.374,22.658ZM22.872,12.2a.674.674,0,0,0-.665.665.656.656,0,0,0,.655.643.634.634,0,0,0,.655-.644A.654.654,0,0,0,22.872,12.2ZM7.171-.123A.546.546,0,0,0,6.613.43a.553.553,0,1,0,1.106,0A.539.539,0,0,0,7.171-.123ZM24.119,9.234a.507.507,0,0,0-.493.488.494.494,0,0,0,.494.494.48.48,0,0,0,.487-.483A.491.491,0,0,0,24.119,9.234Zm-19.454,9.7a.5.5,0,0,0-.488-.488.491.491,0,0,0-.487.5.483.483,0,0,0,.491.479A.49.49,0,0,0,4.665,18.936Z" transform="translate(0 0.123)" fill="#f64060"/>
</svg>
</a>
<a class="circle-link" title="Join the Conversation on Discord" href="https://discord.gg/angular" target="_blank" rel="noopener">
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 245 240">
<title>Discord Logo</title>
<path d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zM140.9 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/>
<path d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/>
</svg>
</a>
</div>
<!-- Footer -->
<footer>
Love Angular?&nbsp;
<a href="https://github.com/angular/angular" target="_blank" rel="noopener"> Give our repo a star.
<div class="github-star-badge">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>
Star
</div>
</a>
<a href="https://github.com/angular/angular" target="_blank" rel="noopener">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" fill="#1976d2"/><path d="M0 0h24v24H0z" fill="none"/></svg>
</a>
</footer>
<svg id="clouds" xmlns="http://www.w3.org/2000/svg" width="2611.084" height="485.677" viewBox="0 0 2611.084 485.677">
<title>Gray Clouds Background</title>
<path id="Path_39" data-name="Path 39" d="M2379.709,863.793c10-93-77-171-168-149-52-114-225-105-264,15-75,3-140,59-152,133-30,2.83-66.725,9.829-93.5,26.25-26.771-16.421-63.5-23.42-93.5-26.25-12-74-77-130-152-133-39-120-212-129-264-15-54.084-13.075-106.753,9.173-138.488,48.9-31.734-39.726-84.4-61.974-138.487-48.9-52-114-225-105-264,15a162.027,162.027,0,0,0-103.147,43.044c-30.633-45.365-87.1-72.091-145.206-58.044-52-114-225-105-264,15-75,3-140,59-152,133-53,5-127,23-130,83-2,42,35,72,70,86,49,20,106,18,157,5a165.625,165.625,0,0,0,120,0c47,94,178,113,251,33,61.112,8.015,113.854-5.72,150.492-29.764a165.62,165.62,0,0,0,110.861-3.236c47,94,178,113,251,33,31.385,4.116,60.563,2.495,86.487-3.311,25.924,5.806,55.1,7.427,86.488,3.311,73,80,204,61,251-33a165.625,165.625,0,0,0,120,0c51,13,108,15,157-5a147.188,147.188,0,0,0,33.5-18.694,147.217,147.217,0,0,0,33.5,18.694c49,20,106,18,157,5a165.625,165.625,0,0,0,120,0c47,94,178,113,251,33C2446.709,1093.793,2554.709,922.793,2379.709,863.793Z" transform="translate(142.69 -634.312)" fill="#eee"/>
</svg>
</div>
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * * The content above * * * * * * * * * * * -->
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * End of Placeholder * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<router-outlet></router-outlet>

View File

@@ -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 { }

View File

@@ -0,0 +1,6 @@
.mat-form-field {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}

View File

@@ -0,0 +1,29 @@
<h2 mat-dialog-title>Ajout d'un utilisateur</h2>
<mat-dialog-content>
<div class="container" fxLayout="column" fxLayoutAlign="space-between center">
<form [formGroup]="userFG">
<mat-form-field>
<input matInput type="text" formControlName="prenom" class="form-control" name="Prenom" placeholder="Prénom" [ngClass]="{'is-invalid':submitted && userFG.controls['prenom'].errors}">
<mat-error *ngIf="userFG.controls['prenom'].hasError('required')">Le prénom est requis !</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput type="text" formControlName="nom" class="form-control" name="Nom" placeholder="Nom" [ngClass]="{'is-invalid':submitted && userFG.controls['nom'].errors}">
<mat-error *ngIf="userFG.controls['nom'].hasError('required')">Le nom est requis !</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput type="text" formControlName="identifiant" class="form-control" name="Identifiant" placeholder="Identifiant" [ngClass]="{'is-invalid':submitted && userFG.controls['identifiant'].errors}">
<mat-error *ngIf="userFG.controls['identifiant'].hasError('required')">L'identifiant est requis !</mat-error>
</mat-form-field>
<mat-form-field>
<mat-select [(value)]="selected" formControlName="role" placeholder="Role">
<mat-option value="Coach">Coach</mat-option>
<mat-option value="Administrateur">Administrateur</mat-option>
</mat-select>
</mat-form-field>
</form>
</div>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="onAbort()">Annuler</button>
<button mat-raised-button color="primary" [disabled]="!userFG.valid" (click)="sendUser(userFG)">OK</button>
</mat-dialog-actions>

View File

@@ -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<AddUserDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ AddUserDialogComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AddUserDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -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<AddUserDialogComponent>) { }
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();
}
}

View File

@@ -0,0 +1,8 @@
<h2 mat-dialog-title>{{title}}</h2>
<mat-dialog-content>
{{text}}
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="sendAnswer(false)">{{labelNOK}}</button>
<button mat-raised-button color="primary" (click)="sendAnswer(true)">{{labelOK}}</button>
</mat-dialog-actions>

View File

@@ -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<ConfirmDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ConfirmDialogComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ConfirmDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -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<ConfirmDialogComponent>
) {
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);
}
}

View File

@@ -0,0 +1 @@
<app-navbar [userInput$]="profile$"></app-navbar>

View File

@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HomeComponent } from './home.component';
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HomeComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -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<Utilisateur>|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;
}
}
}
}

View File

@@ -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%;
}

View File

@@ -0,0 +1,31 @@
<div class="wrapper fadeInDown">
<div id="formContent">
<!-- Tabs Titles -->
<!-- Icon -->
<div class="fadeIn first">
<h4>U10 Manager</h4>
</div>
<!-- Login Form -->
<div class="container" *ngIf="loginFG">
<form [formGroup]="loginFG">
<div class="form-group">
<input type="text" formControlName="login" class="form-control fadeIn second" name="login" placeholder="Identifiant" [ngClass]="{'is-invalid':submitted && loginFG.controls['login'].errors}">
<div *ngIf="submitted && loginFG.controls['login'].errors" class="invalid-feedback">
<div *ngIf="loginFG.controls['login'].errors['required']">L'identifiant est requis !</div>
</div>
</div>
<div class="form-group">
<input type="password" formControlName="password" class="form-control fadeIn third" name="password" placeholder="Mot de passe" [ngClass]="{'is-invalid':submitted && loginFG.controls['password'].errors}">
<div *ngIf="submitted && loginFG.controls['password'].errors" class="invalid-feedback">
<div *ngIf="loginFG.controls['password'].errors['required']">Le mot de passe est requis !</div>
</div>
</div>
<div class="form-group">
<input (click)="onLogin()" type="submit" class="fadeIn fourth" value="Log In">
</div>
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginComponent } from './login.component';
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ LoginComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -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 !");
});
}
}

View File

@@ -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;
* }
*/

View File

@@ -0,0 +1,59 @@
<mat-sidenav-container>
<mat-sidenav mode="side" class="mat-elevation-z8" opened>
<img
class="avatar mat-elevation-z8"
[src]="thumbnail"
/>
<ng-container *ngIf="(userInput$ | async) as profile">
<h4 class="name">{{profile.Prenom}} {{profile.Nom}}</h4>
<p class="designation">{{profile.Role}}</p>
</ng-container>
<mat-divider></mat-divider>
<button mat-button (click)="myAccount()" class="menu-button">
<mat-icon>face</mat-icon>
<span>Mon Compte</span>
</button>
<button mat-button (click)="logout()" class="menu-button">
<mat-icon>eject</mat-icon>
<span>Déconnexion</span>
</button>
<div *ngIf="isAdmin">
<mat-divider></mat-divider>
<button mat-button (click)="modifUsers()" class="menu-button">
<mat-icon>supervisor_account</mat-icon>
<span>Utilisateurs</span>
</button>
</div>
<mat-divider></mat-divider>
<button mat-button class="menu-button" routerLink="/home">
<mat-icon>home</mat-icon>
<span>Home</span>
</button>
<button mat-button (click)="messages()" class="menu-button">
<mat-icon>message</mat-icon>
<span>Messages</span>
</button>
<button mat-button class="menu-button" routerLink="/about">
<mat-icon>info</mat-icon>
<span>About</span>
</button>
<mat-divider></mat-divider>
<button mat-button class="menu-button" routerLink="/help">
<mat-icon>help</mat-icon>
<span>Help</span>
</button>
</mat-sidenav>
<mat-sidenav-content>
<div class="content mat-elevation-z8">
<app-workspace></app-workspace>
</div>
</mat-sidenav-content>
</mat-sidenav-container>

View File

@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NavbarComponent } from './navbar.component';
describe('NavbarComponent', () => {
let component: NavbarComponent;
let fixture: ComponentFixture<NavbarComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ NavbarComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(NavbarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -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<Utilisateur>|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});
}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,15 @@
<div #imessage class="imessage">
<ng-container *ngIf="(messagesInput$ | async) as result">
<ng-container *ngIf="utilisateur">
<ng-container *ngFor="let msg of result">
<p *ngIf="msg.user?.userId === utilisateur.userId" class="from-me">
{{msg.Text}}
</p>
<p *ngIf="msg.user?.userId !== utilisateur.userId" class="from-them">
{{msg.Text}}
<span>{{msg.user?.Prenom}} {{msg.user?.Nom}}</span>
</p>
</ng-container>
</ng-container>
</ng-container>
</div>

View File

@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ChatboxComponent } from './chatbox.component';
describe('ChatboxComponent', () => {
let component: ChatboxComponent;
let fixture: ComponentFixture<ChatboxComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ChatboxComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ChatboxComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -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<Message[]> | 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);
}
}

View File

@@ -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; }*/

View File

@@ -0,0 +1,14 @@
<div class="container" fxLayout="column">
<div class="msg-field" fxFlex="85%">
<app-chatbox [messagesInput$]="messages$"></app-chatbox>
</div>
<div class="msg-box" fxFlex="15%">
<mat-form-field appearance="standard">
<mat-label>Message</mat-label>
<textarea style="resize: none;" matInput placeholder="Entrer votre message à destination des autres utilisateurs ..." [(ngModel)]="msgText" rows="4" fxFlex="97%"></textarea>
<button mat-icon-button mat-suffix (click)="onSend()">
<mat-icon>send</mat-icon>
</button>
</mat-form-field>
</div>
</div>

View File

@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MessagesComponent } from './messages.component';
describe('MessagesComponent', () => {
let component: MessagesComponent;
let fixture: ComponentFixture<MessagesComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ MessagesComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(MessagesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -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<Utilisateur>|null=null;
messages$:Observable<Message[]>|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;
})
);
}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,45 @@
<div *ngIf="userFG; else err">
<div class="container" fxLayout="row" fxLayoutGap="0px">
<mat-card class="mat-elevation-z4">
<img mat-card-image [src]="thumbnail" class="avatar">
<mat-card-actions fxLayout="row" fxLayoutAlign="center">
<input type="file" accept="image/*" class="file-input" (change)="onImageSelected($event)" #fileUpload>
</mat-card-actions>
</mat-card>
<form [formGroup]="userFG">
<mat-form-field>
<input matInput type="text" formControlName="prenom" class="form-control" name="Prenom" placeholder="Prénom" [ngClass]="{'is-invalid':submitted && userFG.controls['prenom'].errors}" (change)="onInputChange($event)">
<mat-error *ngIf="userFG.controls['prenom'].hasError('required')">Le prénom est requis !</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput type="text" formControlName="nom" class="form-control" name="Nom" placeholder="Nom" [ngClass]="{'is-invalid':submitted && userFG.controls['nom'].errors}" (change)="onInputChange($event)">
<mat-error *ngIf="userFG.controls['nom'].hasError('required')">Le nom est requis !</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput type="text" formControlName="identifiant" class="form-control" name="Identifiant" placeholder="Identifiant" [ngClass]="{'is-invalid':submitted && userFG.controls['identifiant'].errors}" (change)="onInputChange($event)">
<mat-error *ngIf="userFG.controls['identifiant'].hasError('required')">L'identifiant est requis !</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput type="password" formControlName="password" class="form-control" name="Password" placeholder="Mot de passe" [ngClass]="{'is-invalid':submitted && userFG.controls['password'].errors}" (change)="onInputChange($event)">
<mat-error *ngIf="userFG.controls['password'].hasError('required')">Le mot de passe est requis !</mat-error>
<mat-error *ngIf="userFG.controls['password'].hasError('pattern')">Le mot de passe doit avoir minimum {{minPw}} caractères et maximum {{maxPw}} avec 1 caratère minuscule, 1 majuscule et 1 chiffre</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput type="password" formControlName="confirm" class="form-control" name="Confirm" placeholder="Confirmer Mot de passe" [ngClass]="{'is-invalid':submitted && userFG.controls['confirm'].errors}" (change)="onInputChange($event)">
<mat-error *ngIf="userFG.controls['confirm'].hasError('required')">La confirmation du mot de passe est requis !</mat-error>
<mat-error *ngIf="userFG.controls['confirm'].hasError('pattern')">Le mot de passe doit avoir minimum {{minPw}} caractères et maximum {{maxPw}} avec 1 caratère minuscule, 1 majuscule et 1 chiffre</mat-error>
<mat-error *ngIf="userFG.controls['confirm'].hasError('mismatch')">La confirmation doit correspondre au mot de passe</mat-error>
</mat-form-field>
</form>
</div>
<div class="container" fxLayout="row" fxLayoutAlign="center">
<button mat-raised-button color="primary" [disabled]="!userFG?.valid" (click)="onModify()">Modifier</button>
</div>
</div>
<ng-template #err>
<div class="container" *ngIf="errFlag" fxLayout="column" fxLayoutAlign="center">
<h1>403</h1>
<h4>INTERDIT</h4>
<p>Vous n'êtes pas autorisé à venir ici !!<p>
</div>
</ng-template>

View File

@@ -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<UserEditComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ UserEditComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(UserEditComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -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 };
}

View File

@@ -0,0 +1,23 @@
<tr class="mat-row" *ngIf="user">
<td class="mat-cell">{{user.Nom}}</td>
<td class="mat-cell">{{user.Prenom}}</td>
<td class="mat-cell">{{user.Identifiant}}</td>
<td class="mat-cell">{{user.Role}}</td>
<td class="mat-cell">
<mat-icon *ngIf="user.Actif">check_circle</mat-icon>
<mat-icon *ngIf="!user.Actif">remove_circle</mat-icon>
</td>
<td class="mat-cell" *ngIf="!isCur">
<button mat-raised-button color="primary" (click)="onResetPasswd()">Reset Mot de passe</button>
</td>
<td class="mat-cell" *ngIf="!isCur">
<button mat-raised-button color="primary" (click)="onActivate(user)">
<span *ngIf="user.Actif">Désactiver</span>
<span *ngIf="!user.Actif">Activer</span>
</button>
</td>
<td class="mat-cell" *ngIf="!isCur">
<button mat-raised-button color="primary" (click)="onDelete()">Supprimer</button>
</td>
</tr>

View File

@@ -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<UserItemComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ UserItemComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(UserItemComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -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 => {
// });
}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,58 @@
<div [hidden]="!isAdmin">
<div class="mat-elevation-z4">
<mat-toolbar color="primary">
<button mat-icon-button matTooltip="Ajouter un utilisteur" (click)="onAddUser()">
<fa-icon [icon]="faUserPlus" class="fa-2x"></fa-icon>
</button>
</mat-toolbar>
<mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="Nom">
<mat-header-cell *matHeaderCellDef mat-sort-header>Nom</mat-header-cell>
<mat-cell *matCellDef="let element">{{element.Nom}}</mat-cell>
</ng-container>
<ng-container matColumnDef="Prenom">
<mat-header-cell *matHeaderCellDef mat-sort-header>Prenom</mat-header-cell>
<mat-cell *matCellDef="let element">{{element.Prenom}}</mat-cell>
</ng-container>
<ng-container matColumnDef="Identifiant">
<mat-header-cell *matHeaderCellDef mat-sort-header>Identifiant</mat-header-cell>
<mat-cell *matCellDef="let element">{{element.Identifiant}}</mat-cell>
</ng-container>
<ng-container matColumnDef="Role">
<mat-header-cell *matHeaderCellDef mat-sort-header>Role</mat-header-cell>
<mat-cell *matCellDef="let element">{{element.Role}}</mat-cell>
</ng-container>
<ng-container matColumnDef="Actif">
<mat-header-cell *matHeaderCellDef mat-sort-header>Actif</mat-header-cell>
<mat-cell *matCellDef="let element">
<mat-icon *ngIf="element.Actif">check_circle</mat-icon>
<mat-icon *ngIf="!element.Actif">remove_circle</mat-icon>
</mat-cell>
</ng-container>
<ng-container matColumnDef="Actions">
<mat-header-cell *matHeaderCellDef>Actions</mat-header-cell>
<mat-cell *matCellDef="let element">
<button mat-mini-fab color="primary" [disabled]="element.userId == curUser.userId" (click)="onActivate(element)">
<span *ngIf="element.Actif"><fa-icon [icon]="faLock"></fa-icon></span>
<span *ngIf="!element.Actif"><fa-icon [icon]='faLockOpen'></fa-icon></span>
</button>
<button mat-mini-fab color="primary" [disabled]="element.userId == curUser.userId" matTooltip="Supprimer l'utilisateur" (click)="onDelete(element)">
<fa-icon [icon]="faTrashCan"></fa-icon>
</button>
<button mat-mini-fab color="primary" [disabled]="element.userId == curUser.userId" (click)="onResetPasswd(element)">
<fa-icon matTooltip="Ré-initialisation du mot de passe" [icon]="faUserSlash"></fa-icon>
</button>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="columnsToDisplay"></mat-header-row>
<mat-row *matRowDef="let myRowData; columns: columnsToDisplay"></mat-row>
</mat-table>
<mat-paginator [pageSizeOptions]="[5]"></mat-paginator>
</div>
</div>

View File

@@ -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<UsersListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ UsersListComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(UsersListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -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<Utilisateur> = new MatTableDataSource<Utilisateur>();
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<any>();
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");
}
);
}
}
);
}
}

View File

@@ -0,0 +1 @@
<router-outlet></router-outlet>

View File

@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { WorkspaceComponent } from './workspace.component';
describe('WorkspaceComponent', () => {
let component: WorkspaceComponent;
let fixture: ComponentFixture<WorkspaceComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ WorkspaceComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(WorkspaceComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -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");
}
}

View File

@@ -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();
});
});

View File

@@ -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<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// 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);
}
}

View File

@@ -0,0 +1,12 @@
export interface User {
Prenom:string;
Nom:string;
userId:number;
}
export interface Message {
msgId?:number;
Text:string;
Date:string;
user?:User;
}

View File

@@ -0,0 +1,10 @@
export interface Utilisateur {
userId:number;
Nom:string;
Prenom:string;
Role:string;
Identifiant:string;
Password:string;
Actif:boolean;
Photo:string;
}

View File

@@ -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<Utilisateur[]> {
let host=environment.host;
const options = {
headers: new HttpHeaders({
'Content-Type' : 'application/json',
}),
withCredentials: true
};
return this.http.get<Utilisateur[]>(host+"/api/utilisateurs", options);
}
deleteUser(id:number):Observable<any> {
let host=environment.host;
const options = {
headers: new HttpHeaders({
'Content-Type' : 'application/json',
}),
withCredentials: true
};
return this.http.delete<any>(host+"/api/utilisateurs/" + id, options);
}
saveUser(val:Utilisateur):Observable<any> {
let host=environment.host;
const options = {
headers: new HttpHeaders({
'Content-Type' : 'application/json',
}),
withCredentials: true
};
return this.http.post<any>(host+"/api/utilisateurs", val, options);
}
getUser(id:number):Observable<Utilisateur> {
let host=environment.host;
const options = {
headers: new HttpHeaders({
'Content-Type' : 'application/json',
}),
withCredentials: true
};
return this.http.get<Utilisateur>(host+"/api/utilisateurs/" + id, options);
}
updateUser(id:number, val:Utilisateur):Observable<any> {
let host=environment.host;
const options = {
headers: new HttpHeaders({
'Content-Type' : 'application/json',
}),
withCredentials: true
};
return this.http.put<any>(host+"/api/utilisateurs/"+id, val, options);
}
updateUserPhoto(id:number, val:FormData):Observable<any> {
let host=environment.host;
return this.http.post<any>(host+"/api/utilisateurs/" + id + "/uploadImage", val, { withCredentials: true });
}
getPhotoUser(id:number):Observable<Blob> {
let host=environment.host;
return this.http.get(host+"/api/utilisateurs/" + id + "/photo", { withCredentials: true, responseType: 'blob'});
}
getCurrentUser():Observable<Utilisateur> {
let host=environment.host;
const options = {
headers: new HttpHeaders({
'Content-Type' : 'application/json',
}),
withCredentials: true
};
return this.http.get<Utilisateur>(host+"/api/utilisateurs/current", options);
}
resetPasswordUser(id:number):Observable<any> {
let host=environment.host;
const options = {
headers: new HttpHeaders({
'Content-Type' : 'application/json',
}),
withCredentials: true
};
return this.http.get<any>(host+"/api/utilisateurs/" + id + "/reset_password", options);
}
activateUser(id:number, val:Utilisateur):Observable<any> {
let host=environment.host;
const options = {
headers: new HttpHeaders({
'Content-Type' : 'application/json',
}),
withCredentials: true
};
return this.http.put<any>(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<any>(host+"/api/utilisateurs/login", {}, options);
}
logoutUser():Observable<any> {
let host=environment.host;
const options = {
headers: new HttpHeaders({
'Content-Type' : 'application/json',
}),
withCredentials: true
};
return this.http.post<any>(host+"/api/utilisateurs/logout", {}, options);
}
getAllMessages():Observable<Message[]> {
let host=environment.host;
const options = {
headers: new HttpHeaders({
'Content-Type' : 'application/json',
}),
withCredentials: true
};
return this.http.get<Message[]>(host+"/api/messages", options);
}
sendMessage(val:Message):Observable<any> {
let host=environment.host;
const options = {
headers: new HttpHeaders({
'Content-Type' : 'application/json',
}),
withCredentials: true
};
return this.http.post<any>(host+"/api/messages", val, options);
}
}

View File

@@ -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<ActionEvt>=new Subject<ActionEvt>();
sourceEventSubjectObservable=this.sourceEventSubject.asObservable();
publishEvent(event:ActionEvt) {
this.sourceEventSubject.next(event);
}
}

View File

@@ -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();
});
});

View File

@@ -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<any> {
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<boolean> {
return new Observable((observer) => {
this.getProfile().subscribe(profile => {
observer.next(true);
observer.complete();
}, error => {
this.router.navigateByUrl("/login");
observer.next(false);
observer.complete();
})
});
}
}

View File

@@ -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<T> {
dataState?:DataStateEnum,
data?:T,
errorMessage?:string
}

View File

@@ -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"
};
/*

View File

@@ -6,6 +6,9 @@
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<app-root></app-root>

View File

@@ -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; }

View File

@@ -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": {