profiles => format clean archi
This commit is contained in:
7
.prettierrc
Normal file
7
.prettierrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"semi": true,
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 2,
|
||||
"printWidth": 100
|
||||
}
|
||||
4
CMD.md
4
CMD.md
@@ -1,3 +1,7 @@
|
||||
# commande Tree
|
||||
```bash
|
||||
tree -a -I 'node_modules|coverage|logs|.vscode|.git|dist|.git|.venv|__pycache__'
|
||||
```
|
||||
# Utilisation de `act` avec des workflows Gitea
|
||||
```bash
|
||||
# Lister les workflows trouvés sous .gitea/workflows
|
||||
|
||||
64
README.md
64
README.md
@@ -2,3 +2,67 @@
|
||||
|
||||
## Description
|
||||
Trouve Ton Profile est une plateforme innovante qui permet à chacun de trouver facilement des professionnels qualifiés dans tous les domaines de la vie quotidienne. Que tu cherches un électricien, un plombier, un développeur, ou tout autre expert, la plateforme te permet de parcourir rapidement des profils, de découvrir leurs compétences, et de sélectionner la personne idéale pour répondre à tes besoins. Conçue pour faciliter l'accès à des talents locaux et spécialisés, Trouve Ton Profile est l'outil incontournable pour simplifier les tâches du quotidien en connectant les personnes à des solutions fiables et adaptées.
|
||||
|
||||
# Architecture du Projet
|
||||
|
||||
- domain : modèles métiers + interfaces de dépôt (ports) + (optionnel) erreurs/valeurs.
|
||||
- usecase : cas d’usage (use cases) qui orchestrent le métier (pur TS).
|
||||
- infrastructure : implémentations techniques (HTTP/PocketBase, mapping DTO ⇄ domaine).
|
||||
- ui (Angular) : composants/containers/facade → appellent les use cases via DI.
|
||||
|
||||
```mathematica
|
||||
┌───────────────────────────────────────────────────┐
|
||||
│ UI (Angular) │
|
||||
│ Composants, Facades, Formulaires │
|
||||
│ ➜ Appelle les Use Cases │
|
||||
└───────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────────────────┐
|
||||
│ Application / Use Cases │
|
||||
│ Exemple : ListProfilesUseCase │
|
||||
│ ➜ Appelle les interfaces du domaine │
|
||||
└───────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────────────────┐
|
||||
│ Domaine (Core) │
|
||||
│ Interfaces (ProfileRepository), Entities │
|
||||
│ ➜ Ne dépend de rien │
|
||||
└───────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────────────────┐
|
||||
│ Infrastructure (Technique) │
|
||||
│ Implémentations : PbProfileRepository │
|
||||
│ API HTTP, PocketBase, LocalStorage │
|
||||
│ ➜ Implémente l’interface du domaine │
|
||||
└───────────────────────────────────────────────────┘
|
||||
|
||||
```
|
||||
# Le présenteurs
|
||||
Un Presenter (ou ViewModel / Mapper) sert à :<br>
|
||||
|
||||
✔ Transformer les données métier (Domain Model) → en données prêtes pour l’affichage (UI Model)<br>
|
||||
✔ Préparer les données : formatage de date, texte tronqué, fusion de champs…<br>
|
||||
✔ Éviter que tes composants Angular fassent eux-mêmes du .toUpperCase(), .slice(), etc.<br>
|
||||
✔ Laisser le domaine pur, sans logique de présentation<br>
|
||||
|
||||
```
|
||||
UI (Component)
|
||||
⬇
|
||||
Facade (coordonne l’action)
|
||||
⬇
|
||||
UseCase (app logic)
|
||||
⬇
|
||||
ProfileRepository (interface domain)
|
||||
⬇
|
||||
Infrastructure (PocketBase / HTTP)
|
||||
⬇
|
||||
▶ DATA RAW (Profile from database)
|
||||
⬆
|
||||
Presenter (transforme en UI-friendly data)
|
||||
⬆
|
||||
UI displays ViewModel
|
||||
|
||||
```
|
||||
|
||||
@@ -93,6 +93,9 @@
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"analytics": "dda3ec82-e13e-4042-ae63-71d138479518"
|
||||
"analytics": "dda3ec82-e13e-4042-ae63-71d138479518",
|
||||
"schematicCollections": [
|
||||
"angular-eslint"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
822
package-lock.json
generated
822
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "trouve-ton-profile",
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "trouve-ton-profile",
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@angular/animations": "^17.0.0",
|
||||
"@angular/common": "^17.0.0",
|
||||
@@ -41,17 +41,18 @@
|
||||
"@angular/cli": "^17.0.1",
|
||||
"@angular/compiler-cli": "^17.0.0",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^18.18.0",
|
||||
"@types/node-fetch": "^2.6.13",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"jasmine-core": "~5.1.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-preset-angular": "^14.6.1",
|
||||
"node-fetch": "^2.7.0",
|
||||
"postcss": "^8.4.47",
|
||||
"prettier": "^3.6.2",
|
||||
"tailwindcss": "^3.4.12",
|
||||
"typescript": "~5.2.2"
|
||||
}
|
||||
@@ -3052,6 +3053,194 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint-community/eslint-utils": {
|
||||
"version": "4.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
|
||||
"integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"eslint-visitor-keys": "^3.4.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint-community/regexpp": {
|
||||
"version": "4.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
|
||||
"integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array": {
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
|
||||
"integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint/object-schema": "^2.1.7",
|
||||
"debug": "^4.3.1",
|
||||
"minimatch": "^3.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-helpers": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.1.tgz",
|
||||
"integrity": "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint/core": "^0.16.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/core": {
|
||||
"version": "0.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz",
|
||||
"integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
|
||||
"integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.4",
|
||||
"debug": "^4.3.2",
|
||||
"espree": "^10.0.1",
|
||||
"globals": "^14.0.0",
|
||||
"ignore": "^5.2.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"minimatch": "^3.1.2",
|
||||
"strip-json-comments": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true,
|
||||
"license": "Python-2.0",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz",
|
||||
"integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://eslint.org/donate"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/object-schema": {
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
|
||||
"integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/plugin-kit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz",
|
||||
"integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint/core": "^0.16.0",
|
||||
"levn": "^0.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/angular-fontawesome": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.14.1.tgz",
|
||||
@@ -3122,6 +3311,62 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/node": {
|
||||
"version": "0.16.7",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
|
||||
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@humanfs/core": "^0.19.1",
|
||||
"@humanwhocodes/retry": "^0.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/module-importer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
|
||||
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12.22"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/nzakas"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/retry": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
|
||||
"integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/nzakas"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
@@ -4862,6 +5107,19 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgr/core": {
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
|
||||
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/pkgr"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz",
|
||||
@@ -5510,13 +5768,6 @@
|
||||
"@types/istanbul-lib-report": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/jasmine": {
|
||||
"version": "5.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.8.tgz",
|
||||
"integrity": "sha512-u7/CnvRdh6AaaIzYjCgUuVbREFgulhX05Qtf6ZtW+aOcjCKKVvKgpkPYJBFTZSHtFBYimzU4zP0V2vrEsq9Wcg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/jest": {
|
||||
"version": "30.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz",
|
||||
@@ -5950,6 +6201,17 @@
|
||||
"acorn": "^8"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-jsx": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
||||
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-walk": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
|
||||
@@ -7701,6 +7963,14 @@
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-is": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/deepmerge": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||
@@ -8300,6 +8570,114 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "9.38.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz",
|
||||
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
"@eslint/config-array": "^0.21.1",
|
||||
"@eslint/config-helpers": "^0.4.1",
|
||||
"@eslint/core": "^0.16.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "9.38.0",
|
||||
"@eslint/plugin-kit": "^0.4.0",
|
||||
"@humanfs/node": "^0.16.6",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@humanwhocodes/retry": "^0.4.2",
|
||||
"@types/estree": "^1.0.6",
|
||||
"ajv": "^6.12.4",
|
||||
"chalk": "^4.0.0",
|
||||
"cross-spawn": "^7.0.6",
|
||||
"debug": "^4.3.2",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"eslint-scope": "^8.4.0",
|
||||
"eslint-visitor-keys": "^4.2.1",
|
||||
"espree": "^10.4.0",
|
||||
"esquery": "^1.5.0",
|
||||
"esutils": "^2.0.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"file-entry-cache": "^8.0.0",
|
||||
"find-up": "^5.0.0",
|
||||
"glob-parent": "^6.0.2",
|
||||
"ignore": "^5.2.0",
|
||||
"imurmurhash": "^0.1.4",
|
||||
"is-glob": "^4.0.0",
|
||||
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"minimatch": "^3.1.2",
|
||||
"natural-compare": "^1.4.0",
|
||||
"optionator": "^0.9.3"
|
||||
},
|
||||
"bin": {
|
||||
"eslint": "bin/eslint.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://eslint.org/donate"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"jiti": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"jiti": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-prettier": {
|
||||
"version": "10.1.8",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
|
||||
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint-config-prettier"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-prettier": {
|
||||
"version": "5.5.4",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz",
|
||||
"integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prettier-linter-helpers": "^1.0.0",
|
||||
"synckit": "^0.11.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint-plugin-prettier"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/eslint": ">=8.0.0",
|
||||
"eslint": ">=8.0.0",
|
||||
"eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
|
||||
"prettier": ">=3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/eslint": {
|
||||
"optional": true
|
||||
},
|
||||
"eslint-config-prettier": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||
@@ -8324,6 +8702,191 @@
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-visitor-keys": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
|
||||
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/escape-string-regexp": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/eslint-scope": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
|
||||
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esrecurse": "^4.3.0",
|
||||
"estraverse": "^5.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/eslint-visitor-keys": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
|
||||
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/find-up": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
||||
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"locate-path": "^6.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/glob-parent": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/eslint/node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"p-locate": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/p-locate": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
|
||||
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"p-limit": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/espree": {
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
|
||||
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"acorn": "^8.15.0",
|
||||
"acorn-jsx": "^5.3.2",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/espree/node_modules/eslint-visitor-keys": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
|
||||
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
@@ -8338,6 +8901,20 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/esquery": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
|
||||
"integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"estraverse": "^5.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/esrecurse": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
|
||||
@@ -8548,6 +9125,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-diff": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
|
||||
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
||||
@@ -8572,6 +9156,14 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-levenshtein": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.19.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
||||
@@ -8621,6 +9213,20 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/file-entry-cache": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
||||
"integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"flat-cache": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
@@ -8707,6 +9313,29 @@
|
||||
"flat": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/flat-cache": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
|
||||
"integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"flatted": "^3.2.9",
|
||||
"keyv": "^4.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
|
||||
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
@@ -9017,6 +9646,20 @@
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "14.0.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
|
||||
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/globby": {
|
||||
"version": "13.2.2",
|
||||
"resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz",
|
||||
@@ -10042,13 +10685,6 @@
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jasmine-core": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.1.2.tgz",
|
||||
"integrity": "sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jest": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
|
||||
@@ -11995,6 +12631,14 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/json-buffer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
||||
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz",
|
||||
@@ -12012,6 +12656,14 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-stable-stringify-without-jsonify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
|
||||
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
@@ -12052,6 +12704,17 @@
|
||||
"source-map-support": "^0.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"json-buffer": "3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@@ -12199,6 +12862,21 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/levn": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"prelude-ls": "^1.2.1",
|
||||
"type-check": "~0.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/license-webpack-plugin": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz",
|
||||
@@ -12290,6 +12968,14 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/log-symbols": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
|
||||
@@ -13539,6 +14225,25 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"deep-is": "^0.1.3",
|
||||
"fast-levenshtein": "^2.0.6",
|
||||
"levn": "^0.4.1",
|
||||
"prelude-ls": "^1.2.1",
|
||||
"type-check": "^0.4.0",
|
||||
"word-wrap": "^1.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ora": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
|
||||
@@ -14445,6 +15150,46 @@
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
||||
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-linter-helpers": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
|
||||
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-diff": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-format": {
|
||||
"version": "30.0.5",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz",
|
||||
@@ -16351,6 +17096,22 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/synckit": {
|
||||
"version": "0.11.11",
|
||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
|
||||
"integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@pkgr/core": "^0.2.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/synckit"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.17",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
||||
@@ -16888,6 +17649,20 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"prelude-ls": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
@@ -18149,6 +18924,17 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wordwrap": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
"start:dev": "docker compose up -d && ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"tsc": "tsc --noEmit",
|
||||
"tsc:watch": "tsc --noEmit --watch",
|
||||
"format": "prettier --write \"src/**/*.{ts,html,scss,css,md,json}\"",
|
||||
"check:all": "npm run format && npm run tsc && npm run lint && npm run test",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:coverage": "jest --coverage",
|
||||
@@ -48,17 +52,18 @@
|
||||
"@angular/cli": "^17.0.1",
|
||||
"@angular/compiler-cli": "^17.0.0",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^18.18.0",
|
||||
"@types/node-fetch": "^2.6.13",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"jasmine-core": "~5.1.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-preset-angular": "^14.6.1",
|
||||
"node-fetch": "^2.7.0",
|
||||
"postcss": "^8.4.47",
|
||||
"prettier": "^3.6.2",
|
||||
"tailwindcss": "^3.4.12",
|
||||
"typescript": "~5.2.2"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<main class="bg-white dark:bg-gray-900" [ngClass]="themeService.darkModeSignal()">
|
||||
<app-nav-bar/>
|
||||
<app-nav-bar />
|
||||
<section class="content bg-white dark:bg-gray-900">
|
||||
<router-outlet></router-outlet>
|
||||
</section>
|
||||
<app-footer/>
|
||||
<app-footer />
|
||||
</main>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
import {provideRouter} from "@angular/router";
|
||||
import {ThemeService} from "@app/core/services/theme/theme.service";
|
||||
import {ProfileDetailComponent} from "@app/routes/profile/profile-detail/profile-detail.component";
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { ThemeService } from '@app/core/services/theme/theme.service';
|
||||
import { ProfileDetailComponent } from '@app/routes/profile/profile-detail/profile-detail.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
let component: AppComponent;
|
||||
@@ -11,9 +11,7 @@ describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AppComponent],
|
||||
providers: [
|
||||
provideRouter([])
|
||||
],
|
||||
providers: [provideRouter([])],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AppComponent);
|
||||
@@ -34,5 +32,4 @@ describe('AppComponent', () => {
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('TrouveTonProfile');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import {Component, inject} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {RouterOutlet} from '@angular/router';
|
||||
import {NavBarComponent} from "@app/shared/components/nav-bar/nav-bar.component";
|
||||
import {FooterComponent} from "@app/shared/components/footer/footer.component";
|
||||
import {ThemeService} from '@app/core/services/theme/theme.service';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import { NavBarComponent } from '@app/shared/components/nav-bar/nav-bar.component';
|
||||
import { FooterComponent } from '@app/shared/components/footer/footer.component';
|
||||
import { ThemeService } from '@app/core/services/theme/theme.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
imports: [CommonModule, RouterOutlet, NavBarComponent, FooterComponent],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.scss'
|
||||
styleUrl: './app.component.scss',
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'TrouveTonProfile';
|
||||
|
||||
@@ -3,9 +3,7 @@ import { provideServerRendering } from '@angular/platform-server';
|
||||
import { appConfig } from './app.config';
|
||||
|
||||
const serverConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideServerRendering()
|
||||
]
|
||||
providers: [provideServerRendering()],
|
||||
};
|
||||
|
||||
export const config = mergeApplicationConfig(appConfig, serverConfig);
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import {ApplicationConfig} from '@angular/core';
|
||||
import { ApplicationConfig } from '@angular/core';
|
||||
import {
|
||||
PreloadAllModules,
|
||||
provideRouter,
|
||||
withInMemoryScrolling,
|
||||
withPreloading,
|
||||
withViewTransitions
|
||||
withViewTransitions,
|
||||
} from '@angular/router';
|
||||
|
||||
import {routes} from './app.routes';
|
||||
import {provideHttpClient, withFetch} from "@angular/common/http";
|
||||
import {provideAnimations} from "@angular/platform-browser/animations";
|
||||
import {provideToastr} from "ngx-toastr";
|
||||
import { routes } from './app.routes';
|
||||
import { provideHttpClient, withFetch } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
import { provideToastr } from 'ngx-toastr';
|
||||
import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-repository.token';
|
||||
import { PbProfileRepository } from '@app/infrastructure/profiles/pb-profile.repository';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
@@ -18,18 +20,18 @@ export const appConfig: ApplicationConfig = {
|
||||
routes,
|
||||
withViewTransitions(),
|
||||
withPreloading(PreloadAllModules),
|
||||
withInMemoryScrolling(
|
||||
{
|
||||
withInMemoryScrolling({
|
||||
scrollPositionRestoration: 'enabled',
|
||||
anchorScrolling: 'enabled',
|
||||
}
|
||||
)),
|
||||
})
|
||||
),
|
||||
provideAnimations(),
|
||||
provideHttpClient(withFetch()),
|
||||
{ provide: PROFILE_REPOSITORY_TOKEN, useExisting: PbProfileRepository },
|
||||
provideToastr({
|
||||
timeOut: 10000,
|
||||
positionClass: 'toast-top-right',
|
||||
preventDuplicates: true,
|
||||
}), // Toastr providers
|
||||
]
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,38 +1,43 @@
|
||||
import {Routes} from '@angular/router';
|
||||
import {authGuard} from "@app/core/guard/authentication/auth.guard";
|
||||
import { Routes } from '@angular/router';
|
||||
import { authGuard } from '@app/core/guard/authentication/auth.guard';
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
title: 'Accueil',
|
||||
loadChildren: () => import('@app/routes/home/home.module').then(m => m.HomeModule)
|
||||
loadChildren: () => import('@app/routes/home/home.module').then((m) => m.HomeModule),
|
||||
},
|
||||
{
|
||||
path: 'home',
|
||||
title: 'Accueil',
|
||||
loadChildren: () => import('@app/routes/home/home.module').then(m => m.HomeModule)
|
||||
loadChildren: () => import('@app/routes/home/home.module').then((m) => m.HomeModule),
|
||||
},
|
||||
{
|
||||
path: 'profiles',
|
||||
title: 'Liste des profiles',
|
||||
loadChildren: () => import('@app/routes/profile/profile.module').then(m => m.ProfileModule)
|
||||
loadChildren: () => import('@app/routes/profile/profile.module').then((m) => m.ProfileModule),
|
||||
},
|
||||
{
|
||||
path: 'auth',
|
||||
title: 'Authentification',
|
||||
loadChildren: () => import('@app/routes/authentification/authentification.module').then(m => m.AuthentificationModule)
|
||||
loadChildren: () =>
|
||||
import('@app/routes/authentification/authentification.module').then(
|
||||
(m) => m.AuthentificationModule
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'my-profile',
|
||||
title: 'Mon profile',
|
||||
canActivate: [authGuard],
|
||||
loadChildren: () => import('@app/routes/my-profile/my-profile.module').then(m => m.MyProfileModule)
|
||||
loadChildren: () =>
|
||||
import('@app/routes/my-profile/my-profile.module').then((m) => m.MyProfileModule),
|
||||
},
|
||||
{
|
||||
path: 'not-found',
|
||||
title: 'Page non trouvée',
|
||||
loadChildren: () => import('@app/routes/not-found/not-found.module').then(m => m.NotFoundModule)
|
||||
loadChildren: () =>
|
||||
import('@app/routes/not-found/not-found.module').then((m) => m.NotFoundModule),
|
||||
},
|
||||
{path: '', redirectTo: '/', pathMatch: 'full'},
|
||||
{path: '**', redirectTo: '/not-found'}
|
||||
{ path: '', redirectTo: '/', pathMatch: 'full' },
|
||||
{ path: '**', redirectTo: '/not-found' },
|
||||
];
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import {authGuard} from './auth.guard';
|
||||
import {AuthService} from "@app/core/services/authentication/auth.service";
|
||||
import {signal} from "@angular/core";
|
||||
import {Auth} from "@app/shared/models/auth";
|
||||
import {CanActivateFn, Router} from '@angular/router';
|
||||
import { authGuard } from './auth.guard';
|
||||
import { AuthService } from '@app/core/services/authentication/auth.service';
|
||||
import { signal } from '@angular/core';
|
||||
import { Auth } from '@app/shared/models/auth';
|
||||
import { CanActivateFn, Router } from '@angular/router';
|
||||
|
||||
describe('authGuard', () => {
|
||||
let mockAuthService: Partial<AuthService>;
|
||||
@@ -14,19 +14,19 @@ describe('authGuard', () => {
|
||||
TestBed.runInInjectionContext(() => authGuard(...guardParameters));
|
||||
|
||||
beforeEach(() => {
|
||||
mockAuthService= {
|
||||
user: signal<Auth | undefined>({ isValid: true, token: 'mockToken', record: null })
|
||||
}
|
||||
mockAuthService = {
|
||||
user: signal<Auth | undefined>({ isValid: true, token: 'mockToken', record: null }),
|
||||
};
|
||||
|
||||
mockRouter ={
|
||||
parseUrl: jest.fn()
|
||||
}
|
||||
mockRouter = {
|
||||
parseUrl: jest.fn(),
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{ provide: AuthService, useValue: mockAuthService },
|
||||
{ provide: Router, useValue: mockRouter }
|
||||
]
|
||||
{ provide: Router, useValue: mockRouter },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('authGuard', () => {
|
||||
const mockRoute = {} as any;
|
||||
const mockState = {} as any;
|
||||
|
||||
const result =TestBed.runInInjectionContext(() => executeGuard(mockRoute, mockState));
|
||||
const result = TestBed.runInInjectionContext(() => executeGuard(mockRoute, mockState));
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
@@ -51,8 +51,7 @@ describe('authGuard', () => {
|
||||
|
||||
const result = TestBed.runInInjectionContext(() => executeGuard(mockRoute, mockState));
|
||||
|
||||
expect(result).toEqual("/auth" as any);
|
||||
expect(mockRouter.parseUrl).toHaveBeenCalledWith("/auth");
|
||||
|
||||
expect(result).toEqual('/auth' as any);
|
||||
expect(mockRouter.parseUrl).toHaveBeenCalledWith('/auth');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import {CanActivateFn, Router} from '@angular/router';
|
||||
import {inject} from "@angular/core";
|
||||
import {AuthService} from "@app/core/services/authentication/auth.service";
|
||||
import { CanActivateFn, Router } from '@angular/router';
|
||||
import { inject } from '@angular/core';
|
||||
import { AuthService } from '@app/core/services/authentication/auth.service';
|
||||
|
||||
export const authGuard: CanActivateFn = (route, state) => {
|
||||
const authService = inject(AuthService);
|
||||
const router = inject(Router);
|
||||
|
||||
if (!authService.user()!.isValid) {
|
||||
return router.parseUrl("/auth")
|
||||
return router.parseUrl('/auth');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -1,46 +1,42 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {Router} from '@angular/router';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import {myProfileResolver} from './my-profile.resolver';
|
||||
import {User} from "@app/shared/models/user";
|
||||
import { myProfileResolver } from './my-profile.resolver';
|
||||
import { User } from '@app/shared/models/user';
|
||||
|
||||
describe('myProfileResolver', () => {
|
||||
|
||||
let mockRouter: Partial<Router>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRouter = {
|
||||
getCurrentNavigation: jest.fn()
|
||||
getCurrentNavigation: jest.fn(),
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{ provide: Router, useValue: mockRouter }
|
||||
]
|
||||
providers: [{ provide: Router, useValue: mockRouter }],
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should return the user from navigation extras state', () => {
|
||||
const user: User = {
|
||||
id: 'adbc123',
|
||||
username: "john_doe",
|
||||
username: 'john_doe',
|
||||
verified: true,
|
||||
emailVisibility: false,
|
||||
email: "jd@example.com",
|
||||
email: 'jd@example.com',
|
||||
created: new Date().toString(),
|
||||
updated: new Date().toString(),
|
||||
name: "john doe",
|
||||
avatar: ""
|
||||
name: 'john doe',
|
||||
avatar: '',
|
||||
};
|
||||
|
||||
// Mocke la méthode getCurrentNavigation pour retourner un objet attendu
|
||||
(mockRouter.getCurrentNavigation as jest.Mock).mockReturnValue({
|
||||
extras: {
|
||||
state: {
|
||||
user
|
||||
}
|
||||
}
|
||||
user,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = TestBed.runInInjectionContext(() => myProfileResolver(null as any, null as any));
|
||||
@@ -48,5 +44,4 @@ describe('myProfileResolver', () => {
|
||||
expect(result).toEqual({ user });
|
||||
expect(mockRouter.getCurrentNavigation).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {ResolveFn, Router} from '@angular/router';
|
||||
import {User} from "@app/shared/models/user";
|
||||
import {inject} from "@angular/core";
|
||||
import { ResolveFn, Router } from '@angular/router';
|
||||
import { User } from '@app/shared/models/user';
|
||||
import { inject } from '@angular/core';
|
||||
|
||||
export const myProfileResolver: ResolveFn<{ user: User }> = (route, state) => {
|
||||
const router = inject(Router);
|
||||
const user: User = router.getCurrentNavigation()!.extras.state!['user'] as User;
|
||||
return {user}
|
||||
return { user };
|
||||
};
|
||||
|
||||
@@ -1,67 +1,61 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {Router} from '@angular/router';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import {detailResolver} from './detail.resolver';
|
||||
import {User} from "@app/shared/models/user";
|
||||
import {Profile} from "@app/shared/models/profile";
|
||||
import { detailResolver } from './detail.resolver';
|
||||
import { User } from '@app/shared/models/user';
|
||||
import { Profile } from '@app/shared/models/profile';
|
||||
|
||||
describe('detailResolver', () => {
|
||||
|
||||
let mockRoute: Partial<Router>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRoute = {
|
||||
getCurrentNavigation: jest.fn()
|
||||
getCurrentNavigation: jest.fn(),
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{ provide: Router, useValue: mockRoute }
|
||||
]
|
||||
providers: [{ provide: Router, useValue: mockRoute }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return user and profile', () => {
|
||||
|
||||
const mockUser : User = {
|
||||
const mockUser: User = {
|
||||
id: 'adbc123',
|
||||
username: "john_doe",
|
||||
username: 'john_doe',
|
||||
verified: true,
|
||||
emailVisibility: false,
|
||||
email: "jd@example.com",
|
||||
email: 'jd@example.com',
|
||||
created: new Date().toString(),
|
||||
updated: new Date().toString(),
|
||||
name: "john doe",
|
||||
avatar: ""
|
||||
name: 'john doe',
|
||||
avatar: '',
|
||||
};
|
||||
const mockProfile : Profile = {
|
||||
id: "string",
|
||||
created: "string",
|
||||
updated: "string",
|
||||
profession: "string",
|
||||
utilisateur: "string",
|
||||
const mockProfile: Profile = {
|
||||
id: 'string',
|
||||
created: 'string',
|
||||
updated: 'string',
|
||||
profession: 'string',
|
||||
utilisateur: 'string',
|
||||
estVerifier: false,
|
||||
secteur: "string",
|
||||
reseaux: JSON.parse("{}"),
|
||||
bio: "string",
|
||||
cv: "string",
|
||||
secteur: 'string',
|
||||
reseaux: JSON.parse('{}'),
|
||||
bio: 'string',
|
||||
cv: 'string',
|
||||
projets: [],
|
||||
apropos: "string"
|
||||
apropos: 'string',
|
||||
};
|
||||
const fakeState = {} as any;
|
||||
const fakeParams = { params: { name: 'john_doe'} } as any;
|
||||
const fakeParams = { params: { name: 'john_doe' } } as any;
|
||||
|
||||
(mockRoute.getCurrentNavigation as jest.Mock).mockReturnValue({
|
||||
extras: {
|
||||
state: { user: mockUser, profile: mockProfile }
|
||||
}
|
||||
state: { user: mockUser, profile: mockProfile },
|
||||
},
|
||||
});
|
||||
|
||||
const result = TestBed.runInInjectionContext(() => detailResolver( fakeParams, fakeState));
|
||||
const result = TestBed.runInInjectionContext(() => detailResolver(fakeParams, fakeState));
|
||||
|
||||
expect(result).toEqual({ user: mockUser, profile: mockProfile });
|
||||
expect(mockRoute.getCurrentNavigation).toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {ResolveFn, Router} from '@angular/router';
|
||||
import {inject} from "@angular/core";
|
||||
import {User} from "@app/shared/models/user";
|
||||
import {Profile} from "@app/shared/models/profile";
|
||||
import { ResolveFn, Router } from '@angular/router';
|
||||
import { inject } from '@angular/core';
|
||||
import { User } from '@app/shared/models/user';
|
||||
import { Profile } from '@app/shared/models/profile';
|
||||
|
||||
export const detailResolver: ResolveFn<{ user:User,profile:Profile }> = (route, state) => {
|
||||
export const detailResolver: ResolveFn<{ user: User; profile: Profile }> = (route, state) => {
|
||||
const paramValue = route.params['name'];
|
||||
const router = inject(Router);
|
||||
return router.getCurrentNavigation()?.extras.state as { user:User,profile:Profile }
|
||||
return router.getCurrentNavigation()?.extras.state as { user: User; profile: Profile };
|
||||
};
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {ResolveFn, Router} from '@angular/router';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { listResolver } from './list.resolver';
|
||||
import {Profile} from "@app/shared/models/profile";
|
||||
import {myProfileResolver} from "@app/core/resolvers/my-profile/my-profile.resolver";
|
||||
import {ProfileService} from "@app/core/services/profile/profile.service";
|
||||
import {Observable, of} from "rxjs";
|
||||
import { Profile } from '@app/shared/models/profile';
|
||||
import { ProfileService } from '@app/core/services/profile/profile.service';
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
describe('listResolver', () => {
|
||||
|
||||
let mockProfileService: Partial<ProfileService>;
|
||||
const routerSpy = {
|
||||
navigate: jest.fn(),
|
||||
@@ -16,31 +14,31 @@ describe('listResolver', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
mockProfileService = {
|
||||
profiles: of([] as Profile[])
|
||||
profiles: of([] as Profile[]),
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{ provide: ProfileService, useValue: mockProfileService },
|
||||
{ provide: Router, useValue: routerSpy }, // <<— spy: neutralise la navigation
|
||||
]
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return profiles observable from ProfileService', () => {
|
||||
const fakeRoute ={ queryParams: { search: '' } } as any;
|
||||
const fakeRoute = { queryParams: { search: '' } } as any;
|
||||
const fakeState = {} as any;
|
||||
|
||||
const expectedProfiles = [] as Profile[];
|
||||
|
||||
const result$ : Observable<Profile[]> = TestBed.runInInjectionContext(() => listResolver(fakeRoute, fakeState) as Observable<Profile[]>);
|
||||
const result$: Observable<Profile[]> = TestBed.runInInjectionContext(
|
||||
() => listResolver(fakeRoute, fakeState) as Observable<Profile[]>
|
||||
);
|
||||
|
||||
result$.subscribe((result:any) => {
|
||||
result$.subscribe((result: any) => {
|
||||
expect(result).toEqual(expectedProfiles);
|
||||
expect(mockProfileService.profiles).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { ResolveFn } from '@angular/router';
|
||||
import {inject} from "@angular/core";
|
||||
import {ProfileService} from "@app/core/services/profile/profile.service";
|
||||
import {Observable} from "rxjs";
|
||||
import {Profile} from "@app/shared/models/profile";
|
||||
import { Profile } from '@app/shared/models/profile';
|
||||
|
||||
export const listResolver: ResolveFn<Observable<Profile[]>> = (route, state) => {
|
||||
export const listResolver: ResolveFn<Profile[]> = (route, state) => {
|
||||
const queryValue = route.queryParams['search'];
|
||||
const profileService = inject(ProfileService);
|
||||
|
||||
return profileService.profiles;
|
||||
return [];
|
||||
};
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import {AuthService} from './auth.service';
|
||||
import {LoginDto} from "@app/shared/models/login-dto";
|
||||
import {RegisterDto} from "@app/shared/models/register-dto";
|
||||
import {User} from "@app/shared/models/user";
|
||||
import {Router} from "@angular/router";
|
||||
import { AuthService } from './auth.service';
|
||||
import { LoginDto } from '@app/shared/models/login-dto';
|
||||
import { RegisterDto } from '@app/shared/models/register-dto';
|
||||
import { User } from '@app/shared/models/user';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
describe('AuthService', () => {
|
||||
let authService: AuthService;
|
||||
let mockLoginUser : LoginDto = {email: 'john_doe@example.com', password: 'mysecretpassword'};
|
||||
let mockRegisterUser: RegisterDto = {email:'john_doe@example.com', password: 'mysecretpassword', passwordConfirm: 'mysecretpassword', emailVisibility: false};
|
||||
const mockLoginUser: LoginDto = { email: 'john_doe@example.com', password: 'mysecretpassword' };
|
||||
const mockRegisterUser: RegisterDto = {
|
||||
email: 'john_doe@example.com',
|
||||
password: 'mysecretpassword',
|
||||
passwordConfirm: 'mysecretpassword',
|
||||
emailVisibility: false,
|
||||
};
|
||||
|
||||
const mockAuth = {
|
||||
isValid: false,
|
||||
record: {email: mockLoginUser.email, id: '12345', verified: false} as User,
|
||||
token: 'mockToken12345'
|
||||
}
|
||||
record: { email: mockLoginUser.email, id: '12345', verified: false } as User,
|
||||
token: 'mockToken12345',
|
||||
};
|
||||
|
||||
const mockAuthStore = {
|
||||
model: {email: mockLoginUser.email, id: '12345', verified: false} as User,
|
||||
model: { email: mockLoginUser.email, id: '12345', verified: false } as User,
|
||||
token: 'abc123',
|
||||
isValid: true,
|
||||
clear: jest.fn(),
|
||||
@@ -26,7 +31,7 @@ describe('AuthService', () => {
|
||||
|
||||
const mockCollection = {
|
||||
authWithPassword: jest.fn().mockResolvedValue({
|
||||
record: { verified: true }
|
||||
record: { verified: true },
|
||||
}),
|
||||
create: jest.fn(),
|
||||
requestPasswordReset: jest.fn(),
|
||||
@@ -48,7 +53,7 @@ describe('AuthService', () => {
|
||||
providers: [
|
||||
{ provide: Router, useValue: routerSpy }, // <<— spy: neutralise la navigation
|
||||
],
|
||||
imports: []
|
||||
imports: [],
|
||||
});
|
||||
authService = TestBed.inject(AuthService);
|
||||
});
|
||||
@@ -66,19 +71,21 @@ describe('AuthService', () => {
|
||||
});
|
||||
|
||||
it('should login correctly and set signal', async () => {
|
||||
authService.login(mockLoginUser)
|
||||
authService
|
||||
.login(mockLoginUser)
|
||||
.then((response) => {
|
||||
expect(response).toBeDefined();
|
||||
}).catch((error) => jest.fn());
|
||||
})
|
||||
.catch((error) => jest.fn());
|
||||
});
|
||||
|
||||
it('should make success register', () => {
|
||||
authService.register(mockRegisterUser)
|
||||
authService
|
||||
.register(mockRegisterUser)
|
||||
.then((response) => {
|
||||
expect(response).toBeDefined();
|
||||
expect(response.email).toEqual(mockRegisterUser.email);
|
||||
}).catch((error) => jest.fn());
|
||||
})
|
||||
.catch((error) => jest.fn());
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
@@ -1,34 +1,32 @@
|
||||
import {Injectable, signal} from '@angular/core';
|
||||
import {LoginDto} from "@app/shared/models/login-dto";
|
||||
import { Injectable, signal } from '@angular/core';
|
||||
import { LoginDto } from '@app/shared/models/login-dto';
|
||||
import PocketBase from 'pocketbase';
|
||||
import {environment} from "@env/environment";
|
||||
import {Auth} from "@app/shared/models/auth";
|
||||
import {RegisterDto} from "@app/shared/models/register-dto";
|
||||
import {User} from "@app/shared/models/user";
|
||||
import { environment } from '@env/environment';
|
||||
import { Auth } from '@app/shared/models/auth';
|
||||
import { RegisterDto } from '@app/shared/models/register-dto';
|
||||
import { User } from '@app/shared/models/user';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AuthService {
|
||||
|
||||
user = signal<Auth | undefined>(undefined)
|
||||
user = signal<Auth | undefined>(undefined);
|
||||
|
||||
async login(loginDto: LoginDto) {
|
||||
const {email, password} = loginDto
|
||||
const { email, password } = loginDto;
|
||||
const pb = new PocketBase(environment.baseUrl);
|
||||
const response = await pb.collection('users').authWithPassword(email, password);
|
||||
const isValid = response.record['verified'];
|
||||
const res = {isValid, record: pb.authStore.model as User, token: pb.authStore.token};
|
||||
if (isValid){
|
||||
const res = { isValid, record: pb.authStore.model as User, token: pb.authStore.token };
|
||||
if (isValid) {
|
||||
this.user.set(res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
async register(registerDto: RegisterDto) {
|
||||
const pb = new PocketBase(environment.baseUrl);
|
||||
return await pb.collection('users').create<User>(registerDto)
|
||||
return await pb.collection('users').create<User>(registerDto);
|
||||
}
|
||||
|
||||
async logout() {
|
||||
@@ -36,10 +34,13 @@ export class AuthService {
|
||||
return pb.authStore.clear();
|
||||
}
|
||||
|
||||
|
||||
updateUser() {
|
||||
const pb = new PocketBase(environment.baseUrl);
|
||||
this.user.set({isValid: pb.authStore.isValid, record: pb.authStore.model as User, token: pb.authStore.token});
|
||||
this.user.set({
|
||||
isValid: pb.authStore.isValid,
|
||||
record: pb.authStore.model as User,
|
||||
token: pb.authStore.token,
|
||||
});
|
||||
}
|
||||
|
||||
sendPasswordReset(email: string) {
|
||||
@@ -49,7 +50,9 @@ export class AuthService {
|
||||
|
||||
verifyEmail(email: string) {
|
||||
const pb = new PocketBase(environment.baseUrl);
|
||||
return pb.collection('users').requestVerification(email)
|
||||
return pb
|
||||
.collection('users')
|
||||
.requestVerification(email)
|
||||
.then(() => {
|
||||
return true;
|
||||
})
|
||||
@@ -66,6 +69,6 @@ export class AuthService {
|
||||
|
||||
isEmailVerified(): boolean {
|
||||
const pb = new PocketBase(environment.baseUrl);
|
||||
return pb.authStore.model? pb.authStore.model['verified'] : false;
|
||||
return pb.authStore.model ? pb.authStore.model['verified'] : false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ProfileService } from './profile.service';
|
||||
import {Router} from "@angular/router";
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
describe('ProfileService', () => {
|
||||
let service: ProfileService;
|
||||
@@ -16,7 +16,7 @@ describe('ProfileService', () => {
|
||||
providers: [
|
||||
{ provide: Router, useValue: routerSpy }, // <<— spy: neutralise la navigation
|
||||
],
|
||||
imports: []
|
||||
imports: [],
|
||||
});
|
||||
service = TestBed.inject(ProfileService);
|
||||
});
|
||||
|
||||
@@ -1,36 +1,31 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import PocketBase from "pocketbase";
|
||||
import {environment} from "@env/environment";
|
||||
import {Profile} from "@app/shared/models/profile";
|
||||
import {from} from "rxjs";
|
||||
import {ProfileDto} from "@app/shared/models/profile-dto";
|
||||
import { Injectable } from '@angular/core';
|
||||
import PocketBase from 'pocketbase';
|
||||
import { environment } from '@env/environment';
|
||||
import { Profile } from '@app/shared/models/profile';
|
||||
import { from } from 'rxjs';
|
||||
import { ProfileDto } from '@app/shared/models/profile-dto';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ProfileService {
|
||||
|
||||
createProfile(profileDto: ProfileDto) {
|
||||
const pb = new PocketBase(environment.baseUrl);
|
||||
return from(
|
||||
pb.collection('profiles').create(profileDto)
|
||||
);
|
||||
return from(pb.collection('profiles').create(profileDto));
|
||||
}
|
||||
|
||||
|
||||
get profiles() {
|
||||
const pb = new PocketBase(environment.baseUrl);
|
||||
return from(
|
||||
pb.collection('profiles').getFullList<Profile>({
|
||||
sort: 'profession'
|
||||
}))
|
||||
sort: 'profession',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getProfileByUserId(userId: string) {
|
||||
const pb = new PocketBase(environment.baseUrl);
|
||||
return from(
|
||||
pb.collection<Profile>('profiles').getFirstListItem(`utilisateur="${userId}"`)
|
||||
)
|
||||
return from(pb.collection<Profile>('profiles').getFirstListItem(`utilisateur="${userId}"`));
|
||||
}
|
||||
|
||||
updateProfile(id: string, data: Profile | any) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ProjectService } from './project.service';
|
||||
import {Router} from "@angular/router";
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
describe('ProjectService', () => {
|
||||
let service: ProjectService;
|
||||
@@ -16,7 +16,7 @@ describe('ProjectService', () => {
|
||||
providers: [
|
||||
{ provide: Router, useValue: routerSpy }, // <<— spy: neutralise la navigation
|
||||
],
|
||||
imports: []
|
||||
imports: [],
|
||||
});
|
||||
service = TestBed.inject(ProjectService);
|
||||
});
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import PocketBase from "pocketbase";
|
||||
import {environment} from "@env/environment.development";
|
||||
import {from} from "rxjs";
|
||||
import {Project} from "@app/shared/models/project";
|
||||
import {ProjectDto} from "@app/shared/models/project-dto";
|
||||
import { Injectable } from '@angular/core';
|
||||
import PocketBase from 'pocketbase';
|
||||
import { environment } from '@env/environment.development';
|
||||
import { from } from 'rxjs';
|
||||
import { Project } from '@app/shared/models/project';
|
||||
import { ProjectDto } from '@app/shared/models/project-dto';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ProjectService {
|
||||
|
||||
createProject(projectDto: ProjectDto) {
|
||||
const pb = new PocketBase(environment.baseUrl);
|
||||
return from(
|
||||
pb.collection('projets').create<Project>(projectDto)
|
||||
);
|
||||
return from(pb.collection('projets').create<Project>(projectDto));
|
||||
}
|
||||
|
||||
getProjectByUserId(userId: string) {
|
||||
const pb = new PocketBase(environment.baseUrl);
|
||||
return from(pb.collection<Project>('projets').getFullList({filter: `utilisateur='${userId}'`}))
|
||||
return from(
|
||||
pb.collection<Project>('projets').getFullList({ filter: `utilisateur='${userId}'` })
|
||||
);
|
||||
}
|
||||
|
||||
getProjectById(id: string) {
|
||||
const pb = new PocketBase(environment.baseUrl);
|
||||
return from(pb.collection<Project>('projets').getOne<Project>(id))
|
||||
return from(pb.collection<Project>('projets').getOne<Project>(id));
|
||||
}
|
||||
|
||||
updateProject(id: string, data: Project | any) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SectorService } from './sector.service';
|
||||
import {Router} from "@angular/router";
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
describe('SectorService', () => {
|
||||
let service: SectorService;
|
||||
@@ -16,7 +16,7 @@ describe('SectorService', () => {
|
||||
providers: [
|
||||
{ provide: Router, useValue: routerSpy }, // <<— spy: neutralise la navigation
|
||||
],
|
||||
imports: []
|
||||
imports: [],
|
||||
});
|
||||
service = TestBed.inject(SectorService);
|
||||
});
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import PocketBase from "pocketbase";
|
||||
import {environment} from "@env/environment.development";
|
||||
import {from} from "rxjs";
|
||||
import {Sector} from "@app/shared/models/sector";
|
||||
import { Injectable } from '@angular/core';
|
||||
import PocketBase from 'pocketbase';
|
||||
import { environment } from '@env/environment.development';
|
||||
import { from } from 'rxjs';
|
||||
import { Sector } from '@app/shared/models/sector';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class SectorService {
|
||||
|
||||
get sectors() {
|
||||
const pb = new PocketBase(environment.baseUrl);
|
||||
return from(
|
||||
pb.collection<Sector>('secteur').getFullList({
|
||||
sort: 'nom'
|
||||
}))
|
||||
sort: 'nom',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
getSectorById(id: string) {
|
||||
const pb = new PocketBase(environment.baseUrl);
|
||||
return from(pb.collection<Sector>('secteur').getOne<Sector>(id))
|
||||
return from(pb.collection<Sector>('secteur').getOne<Sector>(id));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ThemeService } from './theme.service';
|
||||
import {Router} from "@angular/router";
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
describe('ThemeService', () => {
|
||||
let service: ThemeService;
|
||||
@@ -16,7 +16,7 @@ describe('ThemeService', () => {
|
||||
providers: [
|
||||
{ provide: Router, useValue: routerSpy }, // <<— spy: neutralise la navigation
|
||||
],
|
||||
imports: []
|
||||
imports: [],
|
||||
});
|
||||
service = TestBed.inject(ThemeService);
|
||||
});
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import {Injectable, signal} from '@angular/core';
|
||||
import { Injectable, signal } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ThemeService {
|
||||
|
||||
darkModeSignal = signal<string>('null');
|
||||
|
||||
updateDarkMode() {
|
||||
this.darkModeSignal.update((value) => (value === 'dark' ? 'null' : 'dark'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UserService } from './user.service';
|
||||
import {Router} from "@angular/router";
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
describe('UserService', () => {
|
||||
let service: UserService;
|
||||
@@ -16,7 +16,7 @@ describe('UserService', () => {
|
||||
providers: [
|
||||
{ provide: Router, useValue: routerSpy }, // <<— spy: neutralise la navigation
|
||||
],
|
||||
imports: []
|
||||
imports: [],
|
||||
});
|
||||
service = TestBed.inject(UserService);
|
||||
});
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import PocketBase from "pocketbase";
|
||||
import {environment} from "@env/environment";
|
||||
import {from} from "rxjs";
|
||||
import {User} from "@app/shared/models/user";
|
||||
import { Injectable } from '@angular/core';
|
||||
import PocketBase from 'pocketbase';
|
||||
import { environment } from '@env/environment';
|
||||
import { from } from 'rxjs';
|
||||
import { User } from '@app/shared/models/user';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class UserService {
|
||||
|
||||
getUserById(id: string) {
|
||||
const pb = new PocketBase(environment.baseUrl);
|
||||
return from(pb.collection('users').getOne<User>(id));
|
||||
}
|
||||
|
||||
updateUser(id: string, data: User|any) {
|
||||
updateUser(id: string, data: User | any) {
|
||||
const pb = new PocketBase(environment.baseUrl);
|
||||
return from(pb.collection('users').update<User>(id, data));
|
||||
}
|
||||
|
||||
14
src/app/domain/profiles/profile.model.ts
Normal file
14
src/app/domain/profiles/profile.model.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export interface Profile {
|
||||
id: string;
|
||||
created: string;
|
||||
updated: string;
|
||||
profession: string;
|
||||
utilisateur: string;
|
||||
estVerifier: boolean;
|
||||
secteur: string;
|
||||
reseaux: JSON;
|
||||
bio: string;
|
||||
cv: string;
|
||||
projets: string[];
|
||||
apropos: string;
|
||||
}
|
||||
9
src/app/domain/profiles/profile.repository.ts
Normal file
9
src/app/domain/profiles/profile.repository.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Profile } from '@app/domain/profiles/profile.model';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export interface ProfileRepository {
|
||||
list(params?: { search?: string; page?: number; pageSize?: number }): Observable<Profile[]>;
|
||||
getByUserId(userId: string): Observable<Profile | null>;
|
||||
create(profile: Profile): Observable<Profile>;
|
||||
update(id: string, profile: Partial<Profile>): Observable<Profile>;
|
||||
}
|
||||
35
src/app/infrastructure/profiles/pb-profile.repository.ts
Normal file
35
src/app/infrastructure/profiles/pb-profile.repository.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { ProfileRepository } from '@app/domain/profiles/profile.repository';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import { Profile } from '@app/domain/profiles/profile.model';
|
||||
import { Injectable } from '@angular/core';
|
||||
import PocketBase from 'pocketbase';
|
||||
import { environment } from '@env/environment';
|
||||
|
||||
interface ProfileDTO {
|
||||
profession: string;
|
||||
utilisateur: string;
|
||||
reseaux: any;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PbProfileRepository implements ProfileRepository {
|
||||
private pb = new PocketBase(environment.baseUrl);
|
||||
|
||||
list(): Observable<Profile[]> {
|
||||
return from(this.pb.collection('profiles').getFullList<Profile>({ sort: 'profession' }));
|
||||
}
|
||||
|
||||
getByUserId(userId: string): Observable<Profile | null> {
|
||||
return from(
|
||||
this.pb.collection('profiles').getFirstListItem<Profile>(`utilisateur="${userId}"`)
|
||||
);
|
||||
}
|
||||
|
||||
create(profile: ProfileDTO): Observable<Profile> {
|
||||
return from(this.pb.collection('profiles').create<Profile>(profile));
|
||||
}
|
||||
|
||||
update(id: string, data: Partial<Profile>): Observable<Profile> {
|
||||
return from(this.pb.collection('profiles').update<Profile>(id, data));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { InjectionToken } from '@angular/core';
|
||||
import { ProfileRepository } from '@app/domain/profiles/profile.repository';
|
||||
|
||||
export const PROFILE_REPOSITORY_TOKEN = new InjectionToken<ProfileRepository>('ProfileRepository');
|
||||
@@ -1,20 +1,15 @@
|
||||
<section class="relative flex justify-center items-center">
|
||||
|
||||
<div class="">
|
||||
<div class="relative z-10 grid w-full min-h-full py-6 place-content-center">
|
||||
<div class="flex flex-row md:w-[760px] xl:w-[1008px] bg-white rounded-lg overflow-hidden">
|
||||
<section class="hidden md:block md:w-1/2 bg-cover bg-auth">
|
||||
<div
|
||||
class="relative flex items-center justify-center w-full h-full">
|
||||
<div class="relative flex items-center justify-center w-full h-full">
|
||||
<div class="w-[300px] mx-auto space-y-4 p-6 text-gray-50">
|
||||
<h2 class="text-2xl font-extrabold text-center">
|
||||
Bon retour
|
||||
</h2>
|
||||
<h2 class="text-2xl font-extrabold text-center">Bon retour</h2>
|
||||
<p class="text-sm leading-tight text-center">
|
||||
Les solutions à tes besoins sont à portée de main.
|
||||
</p>
|
||||
<div
|
||||
class="absolute space-x-2 text-xs font-bold -translate-x-1/2 bottom-4 left-1/2">
|
||||
<div class="absolute space-x-2 text-xs font-bold -translate-x-1/2 bottom-4 left-1/2">
|
||||
<a href="">Politiques</a>
|
||||
<a href="">Terms</a>
|
||||
<a href="">Conditions</a>
|
||||
@@ -26,17 +21,14 @@
|
||||
<div class="w-[300px] mx-auto space-y-6 p-6">
|
||||
<div class="text-[#002B2F] text-center w-full mb-6">
|
||||
<a [routerLink]="['']" class="inline-block h-[40px]">
|
||||
<h2 class="text-3xl font-bold ">Identifiez-vous</h2>
|
||||
<h2 class="text-3xl font-bold">Identifiez-vous</h2>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<router-outlet/>
|
||||
|
||||
<router-outlet />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AuthComponent } from './auth.component';
|
||||
import {provideRouter} from "@angular/router";
|
||||
import { provideRouter } from '@angular/router';
|
||||
|
||||
describe('AuthComponent', () => {
|
||||
let component: AuthComponent;
|
||||
@@ -10,11 +10,8 @@ describe('AuthComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AuthComponent],
|
||||
providers:[
|
||||
provideRouter([])
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
providers: [provideRouter([])],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AuthComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {RouterLink, RouterOutlet} from "@angular/router";
|
||||
import { RouterLink, RouterOutlet } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-auth',
|
||||
standalone: true,
|
||||
imports: [
|
||||
RouterOutlet,
|
||||
RouterLink
|
||||
],
|
||||
imports: [RouterOutlet, RouterLink],
|
||||
templateUrl: './auth.component.html',
|
||||
styleUrl: './auth.component.scss'
|
||||
styleUrl: './auth.component.scss',
|
||||
})
|
||||
export class AuthComponent {
|
||||
|
||||
}
|
||||
export class AuthComponent {}
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {RegisterComponent} from "@app/shared/features/register/register.component";
|
||||
import {AuthComponent} from "@app/routes/authentification/auth/auth.component";
|
||||
import {LoginComponent} from "@app/shared/features/login/login.component";
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { RegisterComponent } from '@app/shared/features/register/register.component';
|
||||
import { AuthComponent } from '@app/routes/authentification/auth/auth.component';
|
||||
import { LoginComponent } from '@app/shared/features/login/login.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '', component: AuthComponent, children: [
|
||||
|
||||
{path: '', component: LoginComponent},
|
||||
{path: 'register', component: RegisterComponent},
|
||||
]
|
||||
path: '',
|
||||
component: AuthComponent,
|
||||
children: [
|
||||
{ path: '', component: LoginComponent },
|
||||
{ path: 'register', component: RegisterComponent },
|
||||
],
|
||||
},
|
||||
{path: 'not-found', loadChildren: () => import('@app/routes/not-found/not-found.module').then(m => m.NotFoundModule)},
|
||||
{path: '**', redirectTo: 'not-found'}
|
||||
{
|
||||
path: 'not-found',
|
||||
loadChildren: () =>
|
||||
import('@app/routes/not-found/not-found.module').then((m) => m.NotFoundModule),
|
||||
},
|
||||
{ path: '**', redirectTo: 'not-found' },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AuthentificationRoutingModule {
|
||||
}
|
||||
export class AuthentificationRoutingModule {}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
|
||||
import {AuthentificationRoutingModule} from './authentification-routing.module';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { AuthentificationRoutingModule } from './authentification-routing.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
CommonModule,
|
||||
AuthentificationRoutingModule
|
||||
]
|
||||
imports: [CommonModule, AuthentificationRoutingModule],
|
||||
})
|
||||
export class AuthentificationModule { }
|
||||
export class AuthentificationModule {}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import {HomeComponent} from "@app/routes/home/home.component";
|
||||
import { HomeComponent } from '@app/routes/home/home.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{path: '', component: HomeComponent, title: 'Accueil'}
|
||||
];
|
||||
const routes: Routes = [{ path: '', component: HomeComponent, title: 'Accueil' }];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class HomeRoutingModule { }
|
||||
export class HomeRoutingModule {}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<section class="">
|
||||
|
||||
<div class="w-full mx-auto px-4 sm:px-6 lg:px-8 pt-24 pb-10">
|
||||
|
||||
<div class="mt-5 max-w-2xl text-center mx-auto">
|
||||
<h1
|
||||
class="block font-bold text-gray-800 gap-6 dark:text-white text-3xl md:text-5xl lg:text-6xl">
|
||||
class="block font-bold text-gray-800 gap-6 dark:text-white text-3xl md:text-5xl lg:text-6xl"
|
||||
>
|
||||
Dans quel secteur se cache votre prochaine
|
||||
<br>
|
||||
<br />
|
||||
<span class="text-gray-800 dark:text-white">pépites?</span>
|
||||
|
||||
<div class="word-animation max-sm:h-10 max-md:h-11 h-16">
|
||||
@@ -14,15 +13,11 @@
|
||||
<span class="orange">La Santé</span>
|
||||
<span class="blue">Les Etudes</span>
|
||||
</div>
|
||||
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 mx-auto w-full space-y-2">
|
||||
<app-search (onSearchChange)="showNewQuery($event)"/>
|
||||
<app-search (onSearchChange)="showNewQuery($event)" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
@@ -63,5 +63,4 @@ h1 {
|
||||
-webkit-transform-style: translateY(-400%);
|
||||
transform: translateY(-400%);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,9 +8,8 @@ describe('HomeComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [HomeComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
imports: [HomeComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(HomeComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
import {Component, inject} from '@angular/core';
|
||||
import {FaIconComponent} from "@fortawesome/angular-fontawesome";
|
||||
import {SearchComponent} from "@app/shared/features/search/search.component";
|
||||
import {Router} from "@angular/router";
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { SearchComponent } from '@app/shared/features/search/search.component';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
standalone: true,
|
||||
imports: [
|
||||
FaIconComponent,
|
||||
SearchComponent
|
||||
],
|
||||
imports: [FaIconComponent, SearchComponent],
|
||||
templateUrl: './home.component.html',
|
||||
styleUrl: './home.component.scss'
|
||||
styleUrl: './home.component.scss',
|
||||
})
|
||||
export class HomeComponent {
|
||||
|
||||
private readonly router = inject(Router)
|
||||
private readonly router = inject(Router);
|
||||
|
||||
showNewQuery(newQuery: string) {
|
||||
this.router.navigate(['/profiles'], {queryParams: {search: newQuery}});
|
||||
this.router.navigate(['/profiles'], { queryParams: { search: newQuery } });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,8 @@ import { CommonModule } from '@angular/common';
|
||||
|
||||
import { HomeRoutingModule } from './home-routing.module';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
CommonModule,
|
||||
HomeRoutingModule
|
||||
]
|
||||
imports: [CommonModule, HomeRoutingModule],
|
||||
})
|
||||
export class HomeModule { }
|
||||
export class HomeModule {}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {MyProfileComponent} from "@app/routes/my-profile/my-profile.component";
|
||||
import {myProfileResolver} from "@app/core/resolvers/my-profile/my-profile.resolver";
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { MyProfileComponent } from '@app/routes/my-profile/my-profile.component';
|
||||
import { myProfileResolver } from '@app/core/resolvers/my-profile/my-profile.resolver';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '', component: MyProfileComponent, resolve: {user: myProfileResolver}
|
||||
}
|
||||
path: '',
|
||||
component: MyProfileComponent,
|
||||
resolve: { user: myProfileResolver },
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class MyProfileRoutingModule {
|
||||
}
|
||||
export class MyProfileRoutingModule {}
|
||||
|
||||
@@ -1,157 +1,236 @@
|
||||
@if (profile != undefined) {
|
||||
|
||||
<section class="text-gray-600">
|
||||
|
||||
<div class="container px-5 py-5 mx-auto flex flex-col">
|
||||
|
||||
<div class="w-full max-md:mx-5 mx-auto my-5 rounded-lg min-h-56 max-h-64 bg-cover bg-auth">
|
||||
<div class="w-max flex justify-between items-center">
|
||||
<a [routerLink]="['']" (click)="location.back()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
|
||||
class="hover:text-gray-300 text-white size-6 w-5 h-5 sm:w-9 sm:h-9 mx-4 my-4 hover:w-10 hover:h-10 ">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="hover:text-gray-300 text-white size-6 w-5 h-5 sm:w-9 sm:h-9 mx-4 my-4 hover:w-10 hover:h-10"
|
||||
>
|
||||
<title>Retour</title>
|
||||
<path fill-rule="evenodd"
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm-4.28 9.22a.75.75 0 0 0 0 1.06l3 3a.75.75 0 1 0 1.06-1.06l-1.72-1.72h5.69a.75.75 0 0 0 0-1.5h-5.69l1.72-1.72a.75.75 0 0 0-1.06-1.06l-3 3Z"
|
||||
clip-rule="evenodd"/>
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
@if (profile.estVerifier) {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
|
||||
class=" hover:text-purple-300 size-6 text-purple-500 w-6 h-6 sm:w-11 sm:h-11 text-end mx-4 my-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="hover:text-purple-300 size-6 text-purple-500 w-6 h-6 sm:w-11 sm:h-11 text-end mx-4 my-4"
|
||||
>
|
||||
<title>Profile verifier</title>
|
||||
<path fill-rule="evenodd"
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8.603 3.799A4.49 4.49 0 0 1 12 2.25c1.357 0 2.573.6 3.397 1.549a4.49 4.49 0 0 1 3.498 1.307 4.491 4.491 0 0 1 1.307 3.497A4.49 4.49 0 0 1 21.75 12a4.49 4.49 0 0 1-1.549 3.397 4.491 4.491 0 0 1-1.307 3.497 4.491 4.491 0 0 1-3.497 1.307A4.49 4.49 0 0 1 12 21.75a4.49 4.49 0 0 1-3.397-1.549 4.49 4.49 0 0 1-3.498-1.306 4.491 4.491 0 0 1-1.307-3.498A4.49 4.49 0 0 1 2.25 12c0-1.357.6-2.573 1.549-3.397a4.49 4.49 0 0 1 1.307-3.497 4.49 4.49 0 0 1 3.497-1.307Zm7.007 6.387a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
|
||||
clip-rule="evenodd"/>
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-center sm:flex-row sm:justify-between">
|
||||
|
||||
<div class=" text-center sm:pr-8 sm:py-8">
|
||||
<div class="text-center sm:pr-8 sm:py-8">
|
||||
@if (!isEditMode()) {
|
||||
<div class="w-max" (click)="isEditMode.set(!isEditMode())">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
|
||||
class="w-6 h-6 cursor-pointer hover:text-gray-800 dark:text-white">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="w-6 h-6 cursor-pointer hover:text-gray-800 dark:text-white"
|
||||
>
|
||||
<title>Editer le profil</title>
|
||||
<path
|
||||
d="M21.731 2.269a2.625 2.625 0 0 0-3.712 0l-1.157 1.157 3.712 3.712 1.157-1.157a2.625 2.625 0 0 0 0-3.712ZM19.513 8.199l-3.712-3.712-8.4 8.4a5.25 5.25 0 0 0-1.32 2.214l-.8 2.685a.75.75 0 0 0 .933.933l2.685-.8a5.25 5.25 0 0 0 2.214-1.32l8.4-8.4Z"/>
|
||||
d="M21.731 2.269a2.625 2.625 0 0 0-3.712 0l-1.157 1.157 3.712 3.712 1.157-1.157a2.625 2.625 0 0 0 0-3.712ZM19.513 8.199l-3.712-3.712-8.4 8.4a5.25 5.25 0 0 0-1.32 2.214l-.8 2.685a.75.75 0 0 0 .933.933l2.685-.8a5.25 5.25 0 0 0 2.214-1.32l8.4-8.4Z"
|
||||
/>
|
||||
<path
|
||||
d="M5.25 5.25a3 3 0 0 0-3 3v10.5a3 3 0 0 0 3 3h10.5a3 3 0 0 0 3-3V13.5a.75.75 0 0 0-1.5 0v5.25a1.5 1.5 0 0 1-1.5 1.5H5.25a1.5 1.5 0 0 1-1.5-1.5V8.25a1.5 1.5 0 0 1 1.5-1.5h5.25a.75.75 0 0 0 0-1.5H5.25Z"/>
|
||||
d="M5.25 5.25a3 3 0 0 0-3 3v10.5a3 3 0 0 0 3 3h10.5a3 3 0 0 0 3-3V13.5a.75.75 0 0 0-1.5 0v5.25a1.5 1.5 0 0 1-1.5 1.5H5.25a1.5 1.5 0 0 1-1.5-1.5V8.25a1.5 1.5 0 0 1 1.5-1.5h5.25a.75.75 0 0 0 0-1.5H5.25Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@if (!isEditMode()) {
|
||||
<div class="w-28 h-28 rounded-full inline-flex items-center justify-center bg-gray-200 text-gray-400">
|
||||
|
||||
<div
|
||||
class="w-28 h-28 rounded-full inline-flex items-center justify-center bg-gray-200 text-gray-400"
|
||||
>
|
||||
@if (user().avatar) {
|
||||
<img alt="{{user().username}}" class="object-cover object-center h-full w-full rounded-full"
|
||||
src="{{environment.baseUrl}}/api/files/users/{{user().id}}/{{user().avatar}}">
|
||||
|
||||
<img
|
||||
alt="{{ user().username }}"
|
||||
class="object-cover object-center h-full w-full rounded-full"
|
||||
src="{{ environment.baseUrl }}/api/files/users/{{ user().id }}/{{
|
||||
user().avatar
|
||||
}}"
|
||||
/>
|
||||
} @else {
|
||||
<img alt="{{user().username}}" class="object-cover object-center h-full w-full rounded-full"
|
||||
src="https://api.dicebear.com/9.x/adventurer/svg?seed={{user().username}}">
|
||||
<img
|
||||
alt="{{ user().username }}"
|
||||
class="object-cover object-center h-full w-full rounded-full"
|
||||
src="https://api.dicebear.com/9.x/adventurer/svg?seed={{ user().username }}"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full items-center text-center justify-center">
|
||||
@if (user().name) {
|
||||
<h2
|
||||
class="font-medium title-font mt-4 text-gray-900 text-lg w-full dark:text-white">{{ user().name }}</h2>
|
||||
class="font-medium title-font mt-4 text-gray-900 text-lg w-full dark:text-white"
|
||||
>
|
||||
{{ user().name }}
|
||||
</h2>
|
||||
} @else if (user().username) {
|
||||
<h2
|
||||
class="font-medium title-font mt-4 text-gray-900 text-lg w-full dark:text-white">{{ user().username }}</h2>
|
||||
class="font-medium title-font mt-4 text-gray-900 text-lg w-full dark:text-white"
|
||||
>
|
||||
{{ user().username }}
|
||||
</h2>
|
||||
} @else {
|
||||
<h2
|
||||
class="font-medium title-font mt-4 text-gray-900 text-lg w-full dark:text-white">{{ user().email }}</h2>
|
||||
class="font-medium title-font mt-4 text-gray-900 text-lg w-full dark:text-white"
|
||||
>
|
||||
{{ user().email }}
|
||||
</h2>
|
||||
}
|
||||
<div class="w-12 h-1 bg-indigo-500 rounded mt-2 mb-4"></div>
|
||||
@if (profile.bio) {
|
||||
<p class="text-base dark:text-white w-full">{{ profile.bio }}</p>
|
||||
} @else {
|
||||
<p class="text-base dark:text-white w-full">Je suis sur la plateforme Trouve Ton Profile pour partager
|
||||
mon
|
||||
expertise et mes
|
||||
compétences. N’hésitez pas à me contacter pour en savoir plus sur mon parcours et mes domaines
|
||||
d’intervention.</p>
|
||||
|
||||
<p class="text-base dark:text-white w-full">
|
||||
Je suis sur la plateforme Trouve Ton Profile pour partager mon expertise et mes
|
||||
compétences. N’hésitez pas à me contacter pour en savoir plus sur mon parcours et
|
||||
mes domaines d’intervention.
|
||||
</p>
|
||||
}
|
||||
|
||||
@if (profile.secteur) {
|
||||
<div class="space-y-2 flex flex-col my-4">
|
||||
<p class="text-base dark:text-white">Secteur</p>
|
||||
<app-chips [sectorId]="profile.secteur"/>
|
||||
<app-chips [sectorId]="profile.secteur" />
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (profile.reseaux) {
|
||||
<div class="space-y-2 flex flex-col my-4">
|
||||
<p class="text-base dark:text-white">Réseaux</p>
|
||||
<app-reseaux [reseaux]="profile.reseaux"/>
|
||||
<app-reseaux [reseaux]="profile.reseaux" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<a [href]="qrCodeDownloadLink" download="qrcode"
|
||||
class="w-full flex items-center justify-center sm:pr-8 sm:py-8">
|
||||
<qrcode (qrCodeURL)="onChangeURL($event)" [qrdata]="myProfileQrCode" [width]="128" [elementType]="'url'"
|
||||
[errorCorrectionLevel]="'M'" class="mx-auto"></qrcode>
|
||||
<a
|
||||
[href]="qrCodeDownloadLink"
|
||||
download="qrcode"
|
||||
class="w-full flex items-center justify-center sm:pr-8 sm:py-8"
|
||||
>
|
||||
<qrcode
|
||||
(qrCodeURL)="onChangeURL($event)"
|
||||
[qrdata]="myProfileQrCode"
|
||||
[width]="128"
|
||||
[elementType]="'url'"
|
||||
[errorCorrectionLevel]="'M'"
|
||||
class="mx-auto"
|
||||
></qrcode>
|
||||
</a>
|
||||
}
|
||||
|
||||
@if (isEditMode()) {
|
||||
<app-update-user [user]="user()" (isCancelEditMode)="onCancelEditMode($event)"/>
|
||||
<app-update-user [user]="user()" (isCancelEditMode)="onCancelEditMode($event)" />
|
||||
}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex-1 sm:pl-8 sm:py-8 sm:border-l border-gray-200 sm:border-t-0 border-t mt-4 pt-4 sm:mt-0 text-center sm:text-left">
|
||||
|
||||
<div class=" p-4">
|
||||
<ul class="w-full flex flex-wrap gap-x-2 gap-y-2 rounded-lg items-center max-sm:mx-auto">
|
||||
<li id="homeTab" (click)="menu.set('home')"
|
||||
[ngClass]="{'border-blue-600 text-blue-600': menu()=='home'.toLowerCase()}"
|
||||
class="tab flex flex-col justify-center items-center border-2 hover:border-blue-600 rounded-lg bg-gray-100 text-sm font-semibold hover:text-blue-600 py-2 px-2 min-w-[100px] cursor-pointer transition-all">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="w-5 mb-1.5"
|
||||
viewBox="0 0 511 511.999">
|
||||
class="flex-1 sm:pl-8 sm:py-8 sm:border-l border-gray-200 sm:border-t-0 border-t mt-4 pt-4 sm:mt-0 text-center sm:text-left"
|
||||
>
|
||||
<div class="p-4">
|
||||
<ul
|
||||
class="w-full flex flex-wrap gap-x-2 gap-y-2 rounded-lg items-center max-sm:mx-auto"
|
||||
>
|
||||
<li
|
||||
id="homeTab"
|
||||
(click)="menu.set('home')"
|
||||
[ngClass]="{ 'border-blue-600 text-blue-600': menu() == 'home'.toLowerCase() }"
|
||||
class="tab flex flex-col justify-center items-center border-2 hover:border-blue-600 rounded-lg bg-gray-100 text-sm font-semibold hover:text-blue-600 py-2 px-2 min-w-[100px] cursor-pointer transition-all"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
class="w-5 mb-1.5"
|
||||
viewBox="0 0 511 511.999"
|
||||
>
|
||||
<path
|
||||
d="M498.7 222.695c-.016-.011-.028-.027-.04-.039L289.805 13.81C280.902 4.902 269.066 0 256.477 0c-12.59 0-24.426 4.902-33.332 13.809L14.398 222.55c-.07.07-.144.144-.21.215-18.282 18.386-18.25 48.218.09 66.558 8.378 8.383 19.44 13.235 31.273 13.746.484.047.969.07 1.457.07h8.32v153.696c0 30.418 24.75 55.164 55.168 55.164h81.711c8.285 0 15-6.719 15-15V376.5c0-13.879 11.293-25.168 25.172-25.168h48.195c13.88 0 25.168 11.29 25.168 25.168V497c0 8.281 6.715 15 15 15h81.711c30.422 0 55.168-24.746 55.168-55.164V303.14h7.719c12.586 0 24.422-4.903 33.332-13.813 18.36-18.367 18.367-48.254.027-66.633zm-21.243 45.422a17.03 17.03 0 0 1-12.117 5.024h-22.72c-8.285 0-15 6.714-15 15v168.695c0 13.875-11.289 25.164-25.168 25.164h-66.71V376.5c0-30.418-24.747-55.168-55.169-55.168H232.38c-30.422 0-55.172 24.75-55.172 55.168V482h-66.71c-13.876 0-25.169-11.29-25.169-25.164V288.14c0-8.286-6.715-15-15-15H48a13.9 13.9 0 0 0-.703-.032c-4.469-.078-8.66-1.851-11.8-4.996-6.68-6.68-6.68-17.55 0-24.234.003 0 .003-.004.007-.008l.012-.012L244.363 35.02A17.003 17.003 0 0 1 256.477 30c4.574 0 8.875 1.781 12.113 5.02l208.8 208.796.098.094c6.645 6.692 6.633 17.54-.031 24.207zm0 0"
|
||||
data-original="#000000"></path>
|
||||
data-original="#000000"
|
||||
></path>
|
||||
</svg>
|
||||
Mon profile
|
||||
</li>
|
||||
<li id="settingTab" (click)="menu.set('projects')"
|
||||
[ngClass]="{'border-blue-600 text-blue-600': menu()=='projects'.toLowerCase()}"
|
||||
class="tab flex flex-col justify-center items-center border-2 hover:border-blue-600 rounded-lg bg-gray-100 text-sm font-semibold hover:text-blue-600 py-2 px-2 min-w-[100px] cursor-pointer transition-all">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M9.75 3.104v5.714a2.25 2.25 0 0 1-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 0 1 4.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0 1 12 15a9.065 9.065 0 0 0-6.23-.693L5 14.5m14.8.8 1.402 1.402c1.232 1.232.65 3.318-1.067 3.611A48.309 48.309 0 0 1 12 21c-2.773 0-5.491-.235-8.135-.687-1.718-.293-2.3-2.379-1.067-3.61L5 14.5"/>
|
||||
<li
|
||||
id="settingTab"
|
||||
(click)="menu.set('projects')"
|
||||
[ngClass]="{ 'border-blue-600 text-blue-600': menu() == 'projects'.toLowerCase() }"
|
||||
class="tab flex flex-col justify-center items-center border-2 hover:border-blue-600 rounded-lg bg-gray-100 text-sm font-semibold hover:text-blue-600 py-2 px-2 min-w-[100px] cursor-pointer transition-all"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-6"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M9.75 3.104v5.714a2.25 2.25 0 0 1-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 0 1 4.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0 1 12 15a9.065 9.065 0 0 0-6.23-.693L5 14.5m14.8.8 1.402 1.402c1.232 1.232.65 3.318-1.067 3.611A48.309 48.309 0 0 1 12 21c-2.773 0-5.491-.235-8.135-.687-1.718-.293-2.3-2.379-1.067-3.61L5 14.5"
|
||||
/>
|
||||
</svg>
|
||||
Mes projets
|
||||
</li>
|
||||
<li id="profileTab" (click)="menu.set('update')"
|
||||
[ngClass]="{'border-blue-600 text-blue-600': menu()=='update'.toLowerCase()}"
|
||||
class="tab flex flex-col justify-center items-center border-2 hover:border-blue-600 rounded-lg bg-gray-100 text-sm font-semibold hover:text-blue-600 py-2 px-2 min-w-[100px] cursor-pointer transition-all">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="w-5 mb-1.5" viewBox="0 0 512 512">
|
||||
<li
|
||||
id="profileTab"
|
||||
(click)="menu.set('update')"
|
||||
[ngClass]="{ 'border-blue-600 text-blue-600': menu() == 'update'.toLowerCase() }"
|
||||
class="tab flex flex-col justify-center items-center border-2 hover:border-blue-600 rounded-lg bg-gray-100 text-sm font-semibold hover:text-blue-600 py-2 px-2 min-w-[100px] cursor-pointer transition-all"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
class="w-5 mb-1.5"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
d="M437.02 74.98C388.668 26.63 324.379 0 256 0S123.332 26.629 74.98 74.98C26.63 123.332 0 187.621 0 256s26.629 132.668 74.98 181.02C123.332 485.37 187.621 512 256 512s132.668-26.629 181.02-74.98C485.37 388.668 512 324.379 512 256s-26.629-132.668-74.98-181.02zM111.105 429.297c8.454-72.735 70.989-128.89 144.895-128.89 38.96 0 75.598 15.179 103.156 42.734 23.281 23.285 37.965 53.687 41.742 86.152C361.641 462.172 311.094 482 256 482s-105.637-19.824-144.895-52.703zM256 269.507c-42.871 0-77.754-34.882-77.754-77.753C178.246 148.879 213.13 114 256 114s77.754 34.879 77.754 77.754c0 42.871-34.883 77.754-77.754 77.754zm170.719 134.427a175.9 175.9 0 0 0-46.352-82.004c-18.437-18.438-40.25-32.27-64.039-40.938 28.598-19.394 47.426-52.16 47.426-89.238C363.754 132.34 315.414 84 256 84s-107.754 48.34-107.754 107.754c0 37.098 18.844 69.875 47.465 89.266-21.887 7.976-42.14 20.308-59.566 36.542-25.235 23.5-42.758 53.465-50.883 86.348C50.852 364.242 30 312.512 30 256 30 131.383 131.383 30 256 30s226 101.383 226 226c0 56.523-20.86 108.266-55.281 147.934zm0 0"
|
||||
data-original="#000000"></path>
|
||||
data-original="#000000"
|
||||
></path>
|
||||
</svg>
|
||||
Mes informations
|
||||
</li>
|
||||
<li id="cvTab" (click)="menu.set('cv')"
|
||||
[ngClass]="{'border-blue-600 text-blue-600': menu()=='cv'.toLowerCase()}"
|
||||
class="tab flex flex-col justify-center items-center border-2 hover:border-blue-600 rounded-lg bg-gray-100 text-sm font-semibold hover:text-blue-600 py-2 px-2 min-w-[100px] cursor-pointer transition-all">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"/>
|
||||
<li
|
||||
id="cvTab"
|
||||
(click)="menu.set('cv')"
|
||||
[ngClass]="{ 'border-blue-600 text-blue-600': menu() == 'cv'.toLowerCase() }"
|
||||
class="tab flex flex-col justify-center items-center border-2 hover:border-blue-600 rounded-lg bg-gray-100 text-sm font-semibold hover:text-blue-600 py-2 px-2 min-w-[100px] cursor-pointer transition-all"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-6"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
Lecteur de PDF
|
||||
@@ -161,32 +240,29 @@
|
||||
<div id="homeContent" class="tab-content max-w-2xl block mt-8">
|
||||
@switch (menu().toLowerCase()) {
|
||||
@case ('home'.toLowerCase()) {
|
||||
<app-my-home-profile [profile]="profile"/>
|
||||
<app-my-home-profile [profile]="profile" />
|
||||
}
|
||||
@case ('projects'.toLowerCase()) {
|
||||
<app-my-profile-project-list [projectIds]="profile.projets" [userId]="user().id"/>
|
||||
<router-outlet/>
|
||||
<app-my-profile-project-list
|
||||
[projectIds]="profile.projets"
|
||||
[userId]="user().id"
|
||||
/>
|
||||
<router-outlet />
|
||||
}
|
||||
@case ('update'.toLowerCase()) {
|
||||
<app-my-profile-update-form [profile]="profile"/>
|
||||
<app-my-profile-update-form [profile]="profile" />
|
||||
}
|
||||
@case ('cv'.toLowerCase()) {
|
||||
<app-pdf-viewer [profile]="profile"/>
|
||||
<app-pdf-viewer [profile]="profile" />
|
||||
}
|
||||
@default {
|
||||
<app-my-home-profile [profile]="profile"/>
|
||||
<app-my-home-profile [profile]="profile" />
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MyProfileComponent } from './my-profile.component';
|
||||
import {provideRouter} from "@angular/router";
|
||||
import { provideRouter } from '@angular/router';
|
||||
|
||||
describe('MyProfileComponent', () => {
|
||||
let component: MyProfileComponent;
|
||||
@@ -10,11 +10,8 @@ describe('MyProfileComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MyProfileComponent],
|
||||
providers: [
|
||||
provideRouter([])
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
providers: [provideRouter([])],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MyProfileComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
import {Component, computed, inject, OnInit, signal} from '@angular/core';
|
||||
import {ActivatedRoute, RouterLink, RouterOutlet} from "@angular/router";
|
||||
import {User} from "@app/shared/models/user";
|
||||
import {AsyncPipe, JsonPipe, Location, NgClass, UpperCasePipe} from "@angular/common";
|
||||
import {UntilDestroy} from "@ngneat/until-destroy";
|
||||
import {SafeUrl} from "@angular/platform-browser";
|
||||
import {QRCodeModule} from "angularx-qrcode";
|
||||
import {environment} from "@env/environment";
|
||||
import {ChipsComponent} from "@app/shared/components/chips/chips.component";
|
||||
import {ReseauxComponent} from "@app/shared/components/reseaux/reseaux.component";
|
||||
import {UpdateUserComponent} from "@app/shared/features/update-user/update-user.component";
|
||||
import {
|
||||
MyProfileProjectListComponent
|
||||
} from "@app/shared/components/my-profile-project-list/my-profile-project-list.component";
|
||||
import {MyHomeProfileComponent} from "@app/shared/components/my-home-profile/my-home-profile.component";
|
||||
import {
|
||||
MyProfileUpdateFormComponent
|
||||
} from "@app/shared/components/my-profile-update-form/my-profile-update-form.component";
|
||||
import {ProfileService} from "@app/core/services/profile/profile.service";
|
||||
import {Profile} from "@app/shared/models/profile";
|
||||
import {PdfViewerComponent} from "@app/shared/features/pdf-viewer/pdf-viewer.component";
|
||||
import { Component, computed, inject, OnInit, signal } from '@angular/core';
|
||||
import { ActivatedRoute, RouterLink, RouterOutlet } from '@angular/router';
|
||||
import { User } from '@app/shared/models/user';
|
||||
import { AsyncPipe, JsonPipe, Location, NgClass, UpperCasePipe } from '@angular/common';
|
||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||
import { SafeUrl } from '@angular/platform-browser';
|
||||
import { QRCodeModule } from 'angularx-qrcode';
|
||||
import { environment } from '@env/environment';
|
||||
import { ChipsComponent } from '@app/shared/components/chips/chips.component';
|
||||
import { ReseauxComponent } from '@app/shared/components/reseaux/reseaux.component';
|
||||
import { UpdateUserComponent } from '@app/shared/features/update-user/update-user.component';
|
||||
import { MyProfileProjectListComponent } from '@app/shared/components/my-profile-project-list/my-profile-project-list.component';
|
||||
import { MyHomeProfileComponent } from '@app/shared/components/my-home-profile/my-home-profile.component';
|
||||
import { MyProfileUpdateFormComponent } from '@app/shared/components/my-profile-update-form/my-profile-update-form.component';
|
||||
import { ProfileService } from '@app/core/services/profile/profile.service';
|
||||
import { Profile } from '@app/shared/models/profile';
|
||||
import { PdfViewerComponent } from '@app/shared/features/pdf-viewer/pdf-viewer.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-my-profile',
|
||||
@@ -37,20 +33,19 @@ import {PdfViewerComponent} from "@app/shared/features/pdf-viewer/pdf-viewer.com
|
||||
MyHomeProfileComponent,
|
||||
MyProfileUpdateFormComponent,
|
||||
NgClass,
|
||||
PdfViewerComponent
|
||||
PdfViewerComponent,
|
||||
],
|
||||
templateUrl: './my-profile.component.html',
|
||||
styleUrl: './my-profile.component.scss'
|
||||
styleUrl: './my-profile.component.scss',
|
||||
})
|
||||
@UntilDestroy()
|
||||
export class MyProfileComponent implements OnInit {
|
||||
|
||||
private profileService = inject(ProfileService);
|
||||
|
||||
protected readonly environment = environment;
|
||||
protected menu = signal<string>("home");
|
||||
protected menu = signal<string>('home');
|
||||
|
||||
protected myProfileQrCode: string = `${environment.production}`;
|
||||
protected myProfileQrCode = `${environment.production}`;
|
||||
protected qrCodeDownloadLink: SafeUrl = `${environment.production}`;
|
||||
|
||||
protected location = inject(Location);
|
||||
@@ -81,7 +76,7 @@ export class MyProfileComponent implements OnInit {
|
||||
this.profileService.getProfileByUserId(this.user().id).subscribe({
|
||||
next: (value: Profile) => {
|
||||
this.profile = value;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,8 @@ import { CommonModule } from '@angular/common';
|
||||
|
||||
import { MyProfileRoutingModule } from './my-profile-routing.module';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
CommonModule,
|
||||
MyProfileRoutingModule
|
||||
]
|
||||
imports: [CommonModule, MyProfileRoutingModule],
|
||||
})
|
||||
export class MyProfileModule { }
|
||||
export class MyProfileModule {}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import {NotFoundComponent} from "@app/routes/not-found/not-found.component";
|
||||
import { NotFoundComponent } from '@app/routes/not-found/not-found.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{path: '', component: NotFoundComponent, title: 'Page non trouvée'}
|
||||
];
|
||||
const routes: Routes = [{ path: '', component: NotFoundComponent, title: 'Page non trouvée' }];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class NotFoundRoutingModule { }
|
||||
export class NotFoundRoutingModule {}
|
||||
|
||||
@@ -8,9 +8,8 @@ describe('NotFoundComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [NotFoundComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
imports: [NotFoundComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(NotFoundComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -5,8 +5,6 @@ import { Component } from '@angular/core';
|
||||
standalone: true,
|
||||
imports: [],
|
||||
templateUrl: './not-found.component.html',
|
||||
styleUrl: './not-found.component.scss'
|
||||
styleUrl: './not-found.component.scss',
|
||||
})
|
||||
export class NotFoundComponent {
|
||||
|
||||
}
|
||||
export class NotFoundComponent {}
|
||||
|
||||
@@ -3,12 +3,8 @@ import { CommonModule } from '@angular/common';
|
||||
|
||||
import { NotFoundRoutingModule } from './not-found-routing.module';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
CommonModule,
|
||||
NotFoundRoutingModule
|
||||
]
|
||||
imports: [CommonModule, NotFoundRoutingModule],
|
||||
})
|
||||
export class NotFoundModule { }
|
||||
export class NotFoundModule {}
|
||||
|
||||
@@ -4,18 +4,34 @@
|
||||
<div class="rounded-lg min-h-56 max-h-64 overflow-hidden bg-cover bg-auth">
|
||||
<div class="w-full flex justify-between items-center">
|
||||
<a [routerLink]="['/profiles']">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="hover:text-gray-300 text-white size-6 w-5 h-5 sm:w-9 sm:h-9 mx-4 my-4 hover:w-10 hover:h-10 ">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="hover:text-gray-300 text-white size-6 w-5 h-5 sm:w-9 sm:h-9 mx-4 my-4 hover:w-10 hover:h-10"
|
||||
>
|
||||
<title>Retour</title>
|
||||
<path fill-rule="evenodd" d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm-4.28 9.22a.75.75 0 0 0 0 1.06l3 3a.75.75 0 1 0 1.06-1.06l-1.72-1.72h5.69a.75.75 0 0 0 0-1.5h-5.69l1.72-1.72a.75.75 0 0 0-1.06-1.06l-3 3Z" clip-rule="evenodd" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm-4.28 9.22a.75.75 0 0 0 0 1.06l3 3a.75.75 0 1 0 1.06-1.06l-1.72-1.72h5.69a.75.75 0 0 0 0-1.5h-5.69l1.72-1.72a.75.75 0 0 0-1.06-1.06l-3 3Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
@if (profile().estVerifier) {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class=" hover:text-purple-300 size-6 text-purple-500 w-6 h-6 sm:w-11 sm:h-11 text-end mx-4 my-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="hover:text-purple-300 size-6 text-purple-500 w-6 h-6 sm:w-11 sm:h-11 text-end mx-4 my-4"
|
||||
>
|
||||
<title>Profile verifier</title>
|
||||
<path fill-rule="evenodd"
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8.603 3.799A4.49 4.49 0 0 1 12 2.25c1.357 0 2.573.6 3.397 1.549a4.49 4.49 0 0 1 3.498 1.307 4.491 4.491 0 0 1 1.307 3.497A4.49 4.49 0 0 1 21.75 12a4.49 4.49 0 0 1-1.549 3.397 4.491 4.491 0 0 1-1.307 3.497 4.491 4.491 0 0 1-3.497 1.307A4.49 4.49 0 0 1 12 21.75a4.49 4.49 0 0 1-3.397-1.549 4.49 4.49 0 0 1-3.498-1.306 4.491 4.491 0 0 1-1.307-3.498A4.49 4.49 0 0 1 2.25 12c0-1.357.6-2.573 1.549-3.397a4.49 4.49 0 0 1 1.307-3.497 4.49 4.49 0 0 1 3.497-1.307Zm7.007 6.387a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
|
||||
clip-rule="evenodd"/>
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
</div>
|
||||
@@ -24,58 +40,74 @@
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row mt-10">
|
||||
<div class="sm:w-1/3 text-center sm:pr-8 sm:py-8">
|
||||
<div class="w-20 h-20 rounded-full inline-flex items-center justify-center bg-gray-200 text-gray-400">
|
||||
<div
|
||||
class="w-20 h-20 rounded-full inline-flex items-center justify-center bg-gray-200 text-gray-400"
|
||||
>
|
||||
@if (user().avatar) {
|
||||
<img alt="{{user().username}}" class="object-cover object-center h-full w-full rounded-full"
|
||||
src="{{environment.baseUrl}}/api/files/users/{{user().id}}/{{user().avatar}}" loading="lazy">
|
||||
|
||||
<img
|
||||
alt="{{ user().username }}"
|
||||
class="object-cover object-center h-full w-full rounded-full"
|
||||
src="{{ environment.baseUrl }}/api/files/users/{{ user().id }}/{{ user().avatar }}"
|
||||
loading="lazy"
|
||||
/>
|
||||
} @else {
|
||||
<img alt="{{user().username}}" class="object-cover object-center h-full w-full rounded-full"
|
||||
src="https://api.dicebear.com/9.x/adventurer/svg?seed={{user().username}}" loading="lazy">
|
||||
<img
|
||||
alt="{{ user().username }}"
|
||||
class="object-cover object-center h-full w-full rounded-full"
|
||||
src="https://api.dicebear.com/9.x/adventurer/svg?seed={{ user().username }}"
|
||||
loading="lazy"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<div class="flex flex-col items-center text-center justify-center">
|
||||
@if (user().name){
|
||||
<h2 class="font-medium title-font mt-4 text-gray-900 text-lg dark:text-white">{{user().name}}</h2>
|
||||
} @else if (user().username){
|
||||
<h2 class="font-medium title-font mt-4 text-gray-900 text-lg dark:text-white">{{user().username}}</h2>
|
||||
@if (user().name) {
|
||||
<h2 class="font-medium title-font mt-4 text-gray-900 text-lg dark:text-white">
|
||||
{{ user().name }}
|
||||
</h2>
|
||||
} @else if (user().username) {
|
||||
<h2 class="font-medium title-font mt-4 text-gray-900 text-lg dark:text-white">
|
||||
{{ user().username }}
|
||||
</h2>
|
||||
} @else {
|
||||
<h2 class="font-medium title-font mt-4 text-gray-900 text-lg dark:text-white">{{user().email}}</h2>
|
||||
<h2 class="font-medium title-font mt-4 text-gray-900 text-lg dark:text-white">
|
||||
{{ user().email }}
|
||||
</h2>
|
||||
}
|
||||
<div class="w-12 h-1 bg-indigo-500 rounded mt-2 mb-4"></div>
|
||||
@if (profile().bio) {
|
||||
<p class="text-base dark:text-white">{{ profile().bio }}</p>
|
||||
} @else {
|
||||
<p class="text-base dark:text-white">Je suis sur la plateforme Trouve Ton Profile pour partager mon expertise et mes
|
||||
compétences. N’hésitez pas à me contacter pour en savoir plus sur mon parcours et mes domaines
|
||||
d’intervention.</p>
|
||||
|
||||
<p class="text-base dark:text-white">
|
||||
Je suis sur la plateforme Trouve Ton Profile pour partager mon expertise et mes
|
||||
compétences. N’hésitez pas à me contacter pour en savoir plus sur mon parcours et
|
||||
mes domaines d’intervention.
|
||||
</p>
|
||||
}
|
||||
|
||||
@if(profile().secteur){
|
||||
@if (profile().secteur) {
|
||||
<div class="space-y-2 flex flex-col my-4">
|
||||
<p class="text-base dark:text-white">Secteur</p>
|
||||
<app-chips [sectorId]="profile().secteur"/>
|
||||
<app-chips [sectorId]="profile().secteur" />
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (profile().reseaux){
|
||||
@if (profile().reseaux) {
|
||||
<div class="space-y-2 flex flex-col my-4">
|
||||
<p class="text-base dark:text-white">Réseaux</p>
|
||||
<app-reseaux [reseaux]="profile().reseaux"/>
|
||||
<app-reseaux [reseaux]="profile().reseaux" />
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="sm:w-2/3 sm:pl-8 sm:py-8 sm:border-l border-gray-200 sm:border-t-0 border-t mt-4 pt-4 sm:mt-0 text-center sm:text-left flex-col flex space-y-2">
|
||||
|
||||
<h2 class="text-3xl font-extrabold text-black dark:text-white">{{profile().profession | uppercase}}</h2>
|
||||
<p class="leading-relaxed text-lg mb-4 dark:text-white">{{profile().apropos}}</p>
|
||||
|
||||
<app-project-list [userProjectId]="profile().utilisateur"/>
|
||||
class="sm:w-2/3 sm:pl-8 sm:py-8 sm:border-l border-gray-200 sm:border-t-0 border-t mt-4 pt-4 sm:mt-0 text-center sm:text-left flex-col flex space-y-2"
|
||||
>
|
||||
<h2 class="text-3xl font-extrabold text-black dark:text-white">
|
||||
{{ profile().profession | uppercase }}
|
||||
</h2>
|
||||
<p class="leading-relaxed text-lg mb-4 dark:text-white">{{ profile().apropos }}</p>
|
||||
|
||||
<app-project-list [userProjectId]="profile().utilisateur" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import {ProfileDetailComponent} from './profile-detail.component';
|
||||
import {provideRouter} from "@angular/router";
|
||||
import { ProfileDetailComponent } from './profile-detail.component';
|
||||
import { provideRouter } from '@angular/router';
|
||||
|
||||
describe('ProfileDetailComponent', () => {
|
||||
let component: ProfileDetailComponent;
|
||||
@@ -10,11 +10,8 @@ describe('ProfileDetailComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ProfileDetailComponent],
|
||||
providers:[
|
||||
provideRouter([])
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
providers: [provideRouter([])],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ProfileDetailComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import {Component, computed, inject} from '@angular/core';
|
||||
import {ActivatedRoute, RouterLink} from "@angular/router";
|
||||
import {QRCodeModule} from "angularx-qrcode";
|
||||
import {UpperCasePipe} from "@angular/common";
|
||||
import {User} from "@app/shared/models/user";
|
||||
import {Profile} from "@app/shared/models/profile";
|
||||
import {ChipsComponent} from "@app/shared/components/chips/chips.component";
|
||||
import {ReseauxComponent} from "@app/shared/components/reseaux/reseaux.component";
|
||||
import {UntilDestroy} from "@ngneat/until-destroy";
|
||||
import {ProjectListComponent} from "@app/shared/components/project-list/project-list.component";
|
||||
import {environment} from "@env/environment";
|
||||
import { Component, computed, inject } from '@angular/core';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { QRCodeModule } from 'angularx-qrcode';
|
||||
import { UpperCasePipe } from '@angular/common';
|
||||
import { User } from '@app/shared/models/user';
|
||||
import { Profile } from '@app/shared/models/profile';
|
||||
import { ChipsComponent } from '@app/shared/components/chips/chips.component';
|
||||
import { ReseauxComponent } from '@app/shared/components/reseaux/reseaux.component';
|
||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||
import { ProjectListComponent } from '@app/shared/components/project-list/project-list.component';
|
||||
import { environment } from '@env/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-profile-detail',
|
||||
@@ -19,26 +19,23 @@ import {environment} from "@env/environment";
|
||||
ReseauxComponent,
|
||||
RouterLink,
|
||||
UpperCasePipe,
|
||||
ProjectListComponent
|
||||
ProjectListComponent,
|
||||
],
|
||||
templateUrl: './profile-detail.component.html',
|
||||
styleUrl: './profile-detail.component.scss'
|
||||
styleUrl: './profile-detail.component.scss',
|
||||
})
|
||||
@UntilDestroy()
|
||||
export class ProfileDetailComponent {
|
||||
|
||||
protected readonly environment = environment;
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
|
||||
|
||||
protected extraData: { user: User, profile: Profile } = this.route.snapshot.data['profile'];
|
||||
protected extraData: { user: User; profile: Profile } = this.route.snapshot.data['profile'];
|
||||
|
||||
protected user = computed(() => {
|
||||
if (this.extraData != undefined) return this.extraData.user;
|
||||
return {} as User;
|
||||
});
|
||||
|
||||
|
||||
protected profile = computed(() => {
|
||||
if (this.extraData != undefined) return this.extraData.profile;
|
||||
return {} as Profile;
|
||||
|
||||
@@ -1,28 +1,19 @@
|
||||
<section class="pb-10 relative">
|
||||
<div class="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8 pt-24 pb-10 flex sm:flex-row flex-col space-y-2 items-center sm:space-x-4 ">
|
||||
<div
|
||||
class="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8 pt-24 pb-10 flex sm:flex-row flex-col space-y-2 items-center sm:space-x-4"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<app-search/>
|
||||
</div>
|
||||
<div class="">
|
||||
<app-display-profile-card (onDisplayChange)="showNewDisplay($event)"/>
|
||||
<app-search />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="max-w-6xl mx-auto px-4">
|
||||
|
||||
@switch (display()) {
|
||||
@case ('list'.toUpperCase()) {
|
||||
<app-horizental-profile-list [profiles]="profiles"/>
|
||||
@if (loading()) {
|
||||
<div class="flex justify-center items-center h-96">
|
||||
<p>Chargement...</p>
|
||||
</div>
|
||||
} @else {
|
||||
<app-vertical-profile-list [profiles]="profiles()" />
|
||||
}
|
||||
@case ('grid'.toUpperCase()) {
|
||||
<app-vertical-profile-list [profiles]="profiles"/>
|
||||
}
|
||||
@default {
|
||||
<app-vertical-profile-list [profiles]="profiles"/>
|
||||
}
|
||||
}
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ProfileListComponent } from './profile-list.component';
|
||||
import {provideRouter} from "@angular/router";
|
||||
import { provideRouter } from '@angular/router';
|
||||
|
||||
describe('ProfileListComponent', () => {
|
||||
let component: ProfileListComponent;
|
||||
@@ -10,11 +10,8 @@ describe('ProfileListComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ProfileListComponent],
|
||||
providers:[
|
||||
provideRouter([])
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
providers: [provideRouter([])],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ProfileListComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -1,46 +1,25 @@
|
||||
import {Component, inject, signal} from '@angular/core';
|
||||
import {ActivatedRoute} from "@angular/router";
|
||||
import {SearchComponent} from "@app/shared/features/search/search.component";
|
||||
import {
|
||||
HorizentalProfileItemComponent
|
||||
} from "@app/shared/components/horizental-profile-item/horizental-profile-item.component";
|
||||
import {
|
||||
VerticalProfileItemComponent
|
||||
} from "@app/shared/components/vertical-profile-item/vertical-profile-item.component";
|
||||
import {DisplayProfileCardComponent} from "@app/shared/features/display-profile-card/display-profile-card.component";
|
||||
import {JsonPipe} from "@angular/common";
|
||||
import {
|
||||
HorizentalProfileListComponent
|
||||
} from "@app/shared/components/horizental-profile-list/horizental-profile-list.component";
|
||||
import {
|
||||
VerticalProfileListComponent
|
||||
} from "@app/shared/components/vertical-profile-list/vertical-profile-list.component";
|
||||
import {UntilDestroy} from "@ngneat/until-destroy";
|
||||
import {Profile} from "@app/shared/models/profile";
|
||||
import { Component, inject, OnInit } from '@angular/core';
|
||||
import { SearchComponent } from '@app/shared/features/search/search.component';
|
||||
import { DisplayProfileCardComponent } from '@app/shared/features/display-profile-card/display-profile-card.component';
|
||||
import { VerticalProfileListComponent } from '@app/shared/components/vertical-profile-list/vertical-profile-list.component';
|
||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||
import { ProfileFacade } from '@app/ui/profiles/profile.facade';
|
||||
|
||||
@Component({
|
||||
selector: 'app-profile-list',
|
||||
standalone: true,
|
||||
imports: [
|
||||
SearchComponent,
|
||||
HorizentalProfileItemComponent,
|
||||
VerticalProfileItemComponent,
|
||||
DisplayProfileCardComponent,
|
||||
JsonPipe,
|
||||
HorizentalProfileListComponent,
|
||||
VerticalProfileListComponent
|
||||
],
|
||||
imports: [SearchComponent, DisplayProfileCardComponent, VerticalProfileListComponent],
|
||||
templateUrl: './profile-list.component.html',
|
||||
styleUrl: './profile-list.component.scss'
|
||||
styleUrl: './profile-list.component.scss',
|
||||
})
|
||||
@UntilDestroy()
|
||||
export class ProfileListComponent {
|
||||
export class ProfileListComponent implements OnInit {
|
||||
private readonly facade = inject(ProfileFacade);
|
||||
protected readonly profiles = this.facade.profiles;
|
||||
protected readonly loading = this.facade.loading;
|
||||
protected readonly error = this.facade.error;
|
||||
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
protected profiles : Profile[] = this.route.snapshot.data['profiles'] as Profile[];
|
||||
protected display = signal<string>('grid'.toUpperCase());
|
||||
|
||||
showNewDisplay($event: string) {
|
||||
this.display.set($event.toUpperCase())
|
||||
ngOnInit() {
|
||||
this.facade.load();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {ProfileListComponent} from "@app/routes/profile/profile-list/profile-list.component";
|
||||
import {ProfileDetailComponent} from "@app/routes/profile/profile-detail/profile-detail.component";
|
||||
import {listResolver} from "@app/core/resolvers/profile/list/list.resolver";
|
||||
import {detailResolver} from "@app/core/resolvers/profile/detail/detail.resolver";
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { ProfileListComponent } from '@app/routes/profile/profile-list/profile-list.component';
|
||||
import { ProfileDetailComponent } from '@app/routes/profile/profile-detail/profile-detail.component';
|
||||
import { listResolver } from '@app/core/resolvers/profile/list/list.resolver';
|
||||
import { detailResolver } from '@app/core/resolvers/profile/detail/detail.resolver';
|
||||
|
||||
const routes: Routes = [
|
||||
{path: '', component: ProfileListComponent, title: 'Liste des profiles', resolve: {profiles: listResolver}},
|
||||
{path: ':name', component: ProfileDetailComponent, title: 'Detail du profile', resolve: {profile: detailResolver}},
|
||||
{
|
||||
path: '',
|
||||
component: ProfileListComponent,
|
||||
title: 'Liste des profiles',
|
||||
resolve: { profiles: listResolver },
|
||||
},
|
||||
{
|
||||
path: ':name',
|
||||
component: ProfileDetailComponent,
|
||||
title: 'Detail du profile',
|
||||
resolve: { profile: detailResolver },
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class ProfileRoutingModule {
|
||||
}
|
||||
export class ProfileRoutingModule {}
|
||||
|
||||
@@ -3,12 +3,8 @@ import { CommonModule } from '@angular/common';
|
||||
|
||||
import { ProfileRoutingModule } from './profile-routing.module';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ProfileRoutingModule
|
||||
]
|
||||
imports: [CommonModule, ProfileRoutingModule],
|
||||
})
|
||||
export class ProfileModule { }
|
||||
export class ProfileModule {}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<div class="flex flex-wrap space-x-2 items-center space-y-1">
|
||||
@for (chip of sector.nom.split('-'); track chip) {
|
||||
<small
|
||||
class="rounded-full bg-indigo-400 hover:bg-indigo-700 text-xs text-white py-1.5 px-3 font-semibold ">{{ chip | titlecase }}</small>
|
||||
class="rounded-full bg-indigo-400 hover:bg-indigo-700 text-xs text-white py-1.5 px-3 font-semibold"
|
||||
>{{ chip | titlecase }}</small
|
||||
>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,9 +8,8 @@ describe('ChipsComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ChipsComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
imports: [ChipsComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ChipsComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import {Component, inject, Input, OnInit} from '@angular/core';
|
||||
import {Sector} from "@app/shared/models/sector";
|
||||
import {TitleCasePipe} from "@angular/common";
|
||||
import {SectorService} from "@app/core/services/sector/sector.service";
|
||||
import {UntilDestroy} from "@ngneat/until-destroy";
|
||||
import { Component, inject, Input, OnInit } from '@angular/core';
|
||||
import { Sector } from '@app/shared/models/sector';
|
||||
import { TitleCasePipe } from '@angular/common';
|
||||
import { SectorService } from '@app/core/services/sector/sector.service';
|
||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||
|
||||
@Component({
|
||||
selector: 'app-chips',
|
||||
standalone: true,
|
||||
imports: [
|
||||
TitleCasePipe
|
||||
],
|
||||
imports: [TitleCasePipe],
|
||||
templateUrl: './chips.component.html',
|
||||
styleUrl: './chips.component.scss'
|
||||
styleUrl: './chips.component.scss',
|
||||
})
|
||||
@UntilDestroy()
|
||||
export class ChipsComponent implements OnInit {
|
||||
@Input({required: true}) sectorId: string | null = null;
|
||||
@Input({ required: true }) sectorId: string | null = null;
|
||||
|
||||
protected sectorService = inject(SectorService);
|
||||
|
||||
@@ -23,8 +21,6 @@ export class ChipsComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.sectorId)
|
||||
this.sectorService.getSectorById(this.sectorId).subscribe(value => this.sector = value)
|
||||
this.sectorService.getSectorById(this.sectorId).subscribe((value) => (this.sector = value));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
<footer class=" py-4 w-full min-h-full bg-white dark:bg-gray-900 ">
|
||||
<footer class="py-4 w-full min-h-full bg-white dark:bg-gray-900">
|
||||
<div class="max-w-[80rem] mx-auto space-y-6 px-2 md:px-4 lg:px-6 pt-4">
|
||||
<div class="h-px w-full bg-gray-900/10 bg-gray-800 dark:bg-white"></div>
|
||||
<div
|
||||
class="flex flex-col sm:flex-row justify-between items-center text-sm text-gray-600 space-y-4 sm:space-y-0 ">
|
||||
<a
|
||||
[routerLink]="['/']"
|
||||
class="inline-flex items-center gap-1">
|
||||
class="flex flex-col sm:flex-row justify-between items-center text-sm text-gray-600 space-y-4 sm:space-y-0"
|
||||
>
|
||||
<a [routerLink]="['/']" class="inline-flex items-center gap-1">
|
||||
<span class="inline-block text-xl text-gray-800 dark:text-white">TrouveTonProfile</span>
|
||||
</a>
|
||||
|
||||
<ul class="flex items-center gap-4">
|
||||
<li>
|
||||
<a [routerLink]="['/conditions']" class="inline-block text-gray-800 dark:text-white">Conditions</a>
|
||||
<a [routerLink]="['/conditions']" class="inline-block text-gray-800 dark:text-white"
|
||||
>Conditions</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a [routerLink]="['/terms']" class="inline-block text-gray-800 dark:text-white">Terms</a>
|
||||
</li>
|
||||
<li>
|
||||
<a [routerLink]="['/politiques']" class="inline-block text-gray-800 dark:text-white">Politiques</a>
|
||||
<a [routerLink]="['/politiques']" class="inline-block text-gray-800 dark:text-white"
|
||||
>Politiques</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -26,13 +29,16 @@
|
||||
<a
|
||||
target="_blank"
|
||||
href="#"
|
||||
class="inline-block rounded-full h-8 w-8 p-2 border text-gray-800 dark:text-white">
|
||||
class="inline-block rounded-full h-8 w-8 p-2 border text-gray-800 dark:text-white"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-full h-full fill-current"
|
||||
viewBox="0 0 448 512">
|
||||
viewBox="0 0 448 512"
|
||||
>
|
||||
<path
|
||||
d="M100.28 448H7.4V148.9h92.88zM53.79 108.1C24.09 108.1 0 83.5 0 53.8a53.79 53.79 0 0 1 107.58 0c0 29.7-24.1 54.3-53.79 54.3zM447.9 448h-92.68V302.4c0-34.7-.7-79.2-48.29-79.2-48.29 0-55.69 37.7-55.69 76.7V448h-92.78V148.9h89.08v40.8h1.3c12.4-23.5 42.69-48.3 87.88-48.3 94 0 111.28 61.9 111.28 142.3V448z"/>
|
||||
d="M100.28 448H7.4V148.9h92.88zM53.79 108.1C24.09 108.1 0 83.5 0 53.8a53.79 53.79 0 0 1 107.58 0c0 29.7-24.1 54.3-53.79 54.3zM447.9 448h-92.68V302.4c0-34.7-.7-79.2-48.29-79.2-48.29 0-55.69 37.7-55.69 76.7V448h-92.78V148.9h89.08v40.8h1.3c12.4-23.5 42.69-48.3 87.88-48.3 94 0 111.28 61.9 111.28 142.3V448z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
@@ -40,20 +46,19 @@
|
||||
<a
|
||||
target="_blank"
|
||||
href="#"
|
||||
class="inline-block rounded-full h-8 w-8 p-2 border text-gray-800 dark:text-white">
|
||||
class="inline-block rounded-full h-8 w-8 p-2 border text-gray-800 dark:text-white"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-full h-full fill-current"
|
||||
viewBox="0 0 24 24">
|
||||
<g
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2">
|
||||
<path d="M0 0h24v24H0z"/>
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<path d="M0 0h24v24H0z" />
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M18 2a1 1 0 0 1 .993.883L19 3v4a1 1 0 0 1-.883.993L18 8h-3v1h3a1 1 0 0 1 .991 1.131l-.02.112l-1 4a1 1 0 0 1-.858.75L17 15h-2v6a1 1 0 0 1-.883.993L14 22h-4a1 1 0 0 1-.993-.883L9 21v-6H7a1 1 0 0 1-.993-.883L6 14v-4a1 1 0 0 1 .883-.993L7 9h2V8a6 6 0 0 1 5.775-5.996L15 2z"/>
|
||||
d="M18 2a1 1 0 0 1 .993.883L19 3v4a1 1 0 0 1-.883.993L18 8h-3v1h3a1 1 0 0 1 .991 1.131l-.02.112l-1 4a1 1 0 0 1-.858.75L17 15h-2v6a1 1 0 0 1-.883.993L14 22h-4a1 1 0 0 1-.993-.883L9 21v-6H7a1 1 0 0 1-.993-.883L6 14v-4a1 1 0 0 1 .883-.993L7 9h2V8a6 6 0 0 1 5.775-5.996L15 2z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
@@ -62,13 +67,16 @@
|
||||
<a
|
||||
target="_blank"
|
||||
href="#"
|
||||
class="inline-block rounded-full h-8 w-8 p-2 border text-gray-800 dark:text-white">
|
||||
class="inline-block rounded-full h-8 w-8 p-2 border text-gray-800 dark:text-white"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-full h-full fill-current"
|
||||
viewBox="0 0 512 512">
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"/>
|
||||
d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -10,11 +10,8 @@ describe('FooterComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [FooterComponent],
|
||||
providers:[
|
||||
provideRouter([]),
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
providers: [provideRouter([])],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(FooterComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {RouterLink} from "@angular/router";
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-footer',
|
||||
standalone: true,
|
||||
imports: [
|
||||
RouterLink
|
||||
],
|
||||
imports: [RouterLink],
|
||||
templateUrl: './footer.component.html',
|
||||
styleUrl: './footer.component.scss'
|
||||
styleUrl: './footer.component.scss',
|
||||
})
|
||||
export class FooterComponent {
|
||||
|
||||
}
|
||||
export class FooterComponent {}
|
||||
|
||||
@@ -1,43 +1,65 @@
|
||||
@if (user != undefined) {
|
||||
<a [routerLink]="[user.username?user.username:user.id]" [state]="{user,profile}" class="cursor-pointer">
|
||||
<div class="items-center bg-gray-50 rounded-lg shadow sm:flex dark:bg-gray-800 dark:border-gray-700 cursor-pointer">
|
||||
|
||||
<div class="sm:w-max w-full flex items-center justify-center ">
|
||||
<a
|
||||
[routerLink]="[user.username ? user.username : user.id]"
|
||||
[state]="{ user, profile }"
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<div
|
||||
class="items-center bg-gray-50 rounded-lg shadow sm:flex dark:bg-gray-800 dark:border-gray-700 cursor-pointer"
|
||||
>
|
||||
<div class="sm:w-max w-full flex items-center justify-center">
|
||||
@if (user.avatar) {
|
||||
<img class="max-w-xl rounded-lg max-h-64 object-cover sm:rounded-none sm:rounded-l-lg"
|
||||
src="{{environment.baseUrl}}/api/files/users/{{user.id}}/{{user.avatar}}" alt="{{user.username}}"
|
||||
loading="lazy">
|
||||
<img
|
||||
class="max-w-xl rounded-lg max-h-64 object-cover sm:rounded-none sm:rounded-l-lg"
|
||||
src="{{ environment.baseUrl }}/api/files/users/{{ user.id }}/{{ user.avatar }}"
|
||||
alt="{{ user.username }}"
|
||||
loading="lazy"
|
||||
/>
|
||||
} @else {
|
||||
<img class="max-w-xl rounded-lg max-h-64 sm:rounded-none sm:rounded-l-lg"
|
||||
src="https://api.dicebear.com/9.x/adventurer/svg?seed={{user.username}}" alt="{{user.username}}"
|
||||
loading="lazy">
|
||||
<img
|
||||
class="max-w-xl rounded-lg max-h-64 sm:rounded-none sm:rounded-l-lg"
|
||||
src="https://api.dicebear.com/9.x/adventurer/svg?seed={{ user.username }}"
|
||||
alt="{{ user.username }}"
|
||||
loading="lazy"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="p-5 flex flex-col items-center space-y-2">
|
||||
@if (profile.estVerifier) {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6 text-purple-800">
|
||||
<path fill-rule="evenodd"
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="size-6 text-purple-800"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8.603 3.799A4.49 4.49 0 0 1 12 2.25c1.357 0 2.573.6 3.397 1.549a4.49 4.49 0 0 1 3.498 1.307 4.491 4.491 0 0 1 1.307 3.497A4.49 4.49 0 0 1 21.75 12a4.49 4.49 0 0 1-1.549 3.397 4.491 4.491 0 0 1-1.307 3.497 4.491 4.491 0 0 1-3.497 1.307A4.49 4.49 0 0 1 12 21.75a4.49 4.49 0 0 1-3.397-1.549 4.49 4.49 0 0 1-3.498-1.306 4.491 4.491 0 0 1-1.307-3.498A4.49 4.49 0 0 1 2.25 12c0-1.357.6-2.573 1.549-3.397a4.49 4.49 0 0 1 1.307-3.497 4.49 4.49 0 0 1 3.497-1.307Zm7.007 6.387a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
|
||||
clip-rule="evenodd"/>
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
|
||||
@if (user.name) {
|
||||
<h3 class="text-xl font-bold tracking-tight text-gray-900 dark:text-white">{{ user.name }}</h3>
|
||||
<h3 class="text-xl font-bold tracking-tight text-gray-900 dark:text-white">
|
||||
{{ user.name }}
|
||||
</h3>
|
||||
} @else if (user.username) {
|
||||
<h3 class="text-xl font-bold tracking-tight text-gray-900 dark:text-white">{{ user.username }}</h3>
|
||||
<h3 class="text-xl font-bold tracking-tight text-gray-900 dark:text-white">
|
||||
{{ user.username }}
|
||||
</h3>
|
||||
} @else {
|
||||
<h3 class="text-xl font-bold tracking-tight text-gray-900 dark:text-white">Non mentionné</h3>
|
||||
<h3 class="text-xl font-bold tracking-tight text-gray-900 dark:text-white">
|
||||
Non mentionné
|
||||
</h3>
|
||||
}
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ profile.profession }}</span>
|
||||
|
||||
<app-chips [sectorId]="profile.secteur"/>
|
||||
<app-chips [sectorId]="profile.secteur" />
|
||||
|
||||
<app-reseaux [reseaux]="profile.reseaux"/>
|
||||
<app-reseaux [reseaux]="profile.reseaux" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</a>
|
||||
|
||||
}
|
||||
|
||||
@@ -8,9 +8,8 @@ describe('HorizentalProfileItemComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [HorizentalProfileItemComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
imports: [HorizentalProfileItemComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(HorizentalProfileItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -1,36 +1,30 @@
|
||||
import {Component, inject, Input, OnInit} from '@angular/core';
|
||||
import {Router, RouterLink} from "@angular/router";
|
||||
import {Profile} from "@app/shared/models/profile";
|
||||
import {UserService} from "@app/core/services/user/user.service";
|
||||
import {User} from "@app/shared/models/user";
|
||||
import {ChipsComponent} from "@app/shared/components/chips/chips.component";
|
||||
import {ReseauxComponent} from "@app/shared/components/reseaux/reseaux.component";
|
||||
import {environment} from "@env/environment";
|
||||
import { Component, inject, Input, OnInit } from '@angular/core';
|
||||
import { Router, RouterLink } from '@angular/router';
|
||||
import { UserService } from '@app/core/services/user/user.service';
|
||||
import { User } from '@app/shared/models/user';
|
||||
import { ChipsComponent } from '@app/shared/components/chips/chips.component';
|
||||
import { ReseauxComponent } from '@app/shared/components/reseaux/reseaux.component';
|
||||
import { environment } from '@env/environment';
|
||||
import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-horizental-profile-item',
|
||||
standalone: true,
|
||||
imports: [
|
||||
RouterLink,
|
||||
ChipsComponent,
|
||||
ReseauxComponent
|
||||
],
|
||||
imports: [RouterLink, ChipsComponent, ReseauxComponent],
|
||||
templateUrl: './horizental-profile-item.component.html',
|
||||
styleUrl: './horizental-profile-item.component.scss'
|
||||
styleUrl: './horizental-profile-item.component.scss',
|
||||
})
|
||||
export class HorizentalProfileItemComponent implements OnInit {
|
||||
|
||||
@Input({required: true}) profile: Profile = {} as Profile;
|
||||
protected router = inject(Router)
|
||||
@Input({ required: true }) profile: ProfileViewModel = {} as ProfileViewModel;
|
||||
protected router = inject(Router);
|
||||
protected userService = inject(UserService);
|
||||
|
||||
protected user: User | undefined = undefined;
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
this.userService.getUserById(this.profile.utilisateur).subscribe(
|
||||
value => this.user = value
|
||||
)
|
||||
this.userService
|
||||
.getUserById(this.profile.utilisateur)
|
||||
.subscribe((value) => (this.user = value));
|
||||
}
|
||||
|
||||
protected readonly environment = environment;
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
<section class="bg-white dark:bg-gray-900">
|
||||
<div class="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16 lg:px-6 ">
|
||||
<div class="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16 lg:px-6">
|
||||
<div class="grid gap-8 mb-6 lg:mb-16 md:grid-cols-2">
|
||||
|
||||
@for (profile of profiles; track profile.id) {
|
||||
<app-horizental-profile-item [profile]="profile"/>
|
||||
<app-horizental-profile-item [profile]="profile" />
|
||||
} @empty {
|
||||
<p>Aucun profile trouvée</p>
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -8,9 +8,8 @@ describe('HorizentalProfileListComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [HorizentalProfileListComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
imports: [HorizentalProfileListComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(HorizentalProfileListComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {
|
||||
HorizentalProfileItemComponent
|
||||
} from "@app/shared/components/horizental-profile-item/horizental-profile-item.component";
|
||||
import {Profile} from "@app/shared/models/profile";
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { HorizentalProfileItemComponent } from '@app/shared/components/horizental-profile-item/horizental-profile-item.component';
|
||||
import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-horizental-profile-list',
|
||||
standalone: true,
|
||||
imports: [
|
||||
HorizentalProfileItemComponent
|
||||
],
|
||||
imports: [HorizentalProfileItemComponent],
|
||||
templateUrl: './horizental-profile-list.component.html',
|
||||
styleUrl: './horizental-profile-list.component.scss'
|
||||
styleUrl: './horizental-profile-list.component.scss',
|
||||
})
|
||||
export class HorizentalProfileListComponent {
|
||||
@Input({required:true}) profiles: Profile[] = []
|
||||
@Input({ required: true }) profiles: ProfileViewModel[] = [];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
@if (profile != undefined) {
|
||||
<h2 class="text-3xl font-extrabold text-black dark:text-white">{{ profile.profession | uppercase }}</h2>
|
||||
<h2 class="text-3xl font-extrabold text-black dark:text-white">
|
||||
{{ profile.profession | uppercase }}
|
||||
</h2>
|
||||
<p class="leading-relaxed text-lg mb-4 dark:text-white">{{ profile.apropos }}</p>
|
||||
}
|
||||
|
||||
@@ -8,9 +8,8 @@ describe('MyHomeProfileComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MyHomeProfileComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
imports: [MyHomeProfileComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MyHomeProfileComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {AsyncPipe, JsonPipe, UpperCasePipe} from "@angular/common";
|
||||
import {Profile} from "@app/shared/models/profile";
|
||||
import {UntilDestroy} from "@ngneat/until-destroy";
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { AsyncPipe, JsonPipe, UpperCasePipe } from '@angular/common';
|
||||
import { Profile } from '@app/shared/models/profile';
|
||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||
|
||||
@Component({
|
||||
selector: 'app-my-home-profile',
|
||||
standalone: true,
|
||||
imports: [
|
||||
UpperCasePipe,
|
||||
AsyncPipe,
|
||||
JsonPipe
|
||||
],
|
||||
imports: [UpperCasePipe, AsyncPipe, JsonPipe],
|
||||
templateUrl: './my-home-profile.component.html',
|
||||
styleUrl: './my-home-profile.component.scss'
|
||||
styleUrl: './my-home-profile.component.scss',
|
||||
})
|
||||
@UntilDestroy()
|
||||
export class MyHomeProfileComponent {
|
||||
@Input({required: true}) profile: Profile | undefined = undefined
|
||||
@Input({ required: true }) profile: Profile | undefined = undefined;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
@if (project) {
|
||||
<div class="bg-white rounded-2xl border p-6 max-w-sm">
|
||||
|
||||
<div class="">
|
||||
<h3 class="text-lg font-bold text-gray-800 mb-3">{{ project.nom }}</h3>
|
||||
<p class="text-gray-800 text-sm">{{ project.description }}</p>
|
||||
<div class="mt-6">
|
||||
|
||||
<a [routerLink]="[]"
|
||||
class="flex items-center flex-wrap justify-between gap-2 border rounded-3xl pl-5 pr-3 h-14 w-full hover:bg-purple-100 transition-all duration-300">
|
||||
<a
|
||||
[routerLink]="[]"
|
||||
class="flex items-center flex-wrap justify-between gap-2 border rounded-3xl pl-5 pr-3 h-14 w-full hover:bg-purple-100 transition-all duration-300"
|
||||
>
|
||||
Modifier
|
||||
<div class="w-11 h-11 rounded-full bg-purple-200 flex justify-center items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5 cursor-pointer">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="w-5 h-5 cursor-pointer"
|
||||
>
|
||||
<path
|
||||
d="M21.731 2.269a2.625 2.625 0 0 0-3.712 0l-1.157 1.157 3.712 3.712 1.157-1.157a2.625 2.625 0 0 0 0-3.712ZM19.513 8.199l-3.712-3.712-8.4 8.4a5.25 5.25 0 0 0-1.32 2.214l-.8 2.685a.75.75 0 0 0 .933.933l2.685-.8a5.25 5.25 0 0 0 2.214-1.32l8.4-8.4Z"/>
|
||||
d="M21.731 2.269a2.625 2.625 0 0 0-3.712 0l-1.157 1.157 3.712 3.712 1.157-1.157a2.625 2.625 0 0 0 0-3.712ZM19.513 8.199l-3.712-3.712-8.4 8.4a5.25 5.25 0 0 0-1.32 2.214l-.8 2.685a.75.75 0 0 0 .933.933l2.685-.8a5.25 5.25 0 0 0 2.214-1.32l8.4-8.4Z"
|
||||
/>
|
||||
<path
|
||||
d="M5.25 5.25a3 3 0 0 0-3 3v10.5a3 3 0 0 0 3 3h10.5a3 3 0 0 0 3-3V13.5a.75.75 0 0 0-1.5 0v5.25a1.5 1.5 0 0 1-1.5 1.5H5.25a1.5 1.5 0 0 1-1.5-1.5V8.25a1.5 1.5 0 0 1 1.5-1.5h5.25a.75.75 0 0 0 0-1.5H5.25Z"/>
|
||||
d="M5.25 5.25a3 3 0 0 0-3 3v10.5a3 3 0 0 0 3 3h10.5a3 3 0 0 0 3-3V13.5a.75.75 0 0 0-1.5 0v5.25a1.5 1.5 0 0 1-1.5 1.5H5.25a1.5 1.5 0 0 1-1.5-1.5V8.25a1.5 1.5 0 0 1 1.5-1.5h5.25a.75.75 0 0 0 0-1.5H5.25Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,9 +8,8 @@ describe('MyProfileProjectItemComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MyProfileProjectItemComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
imports: [MyProfileProjectItemComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MyProfileProjectItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -1,32 +1,27 @@
|
||||
import {Component, inject, Input, OnInit} from '@angular/core';
|
||||
import {AuthService} from "@app/core/services/authentication/auth.service";
|
||||
import {ProjectService} from "@app/core/services/project/project.service";
|
||||
import {Project} from "@app/shared/models/project";
|
||||
import {environment} from "@env/environment";
|
||||
import {RouterLink} from "@angular/router";
|
||||
import { Component, inject, Input, OnInit } from '@angular/core';
|
||||
import { AuthService } from '@app/core/services/authentication/auth.service';
|
||||
import { ProjectService } from '@app/core/services/project/project.service';
|
||||
import { Project } from '@app/shared/models/project';
|
||||
import { environment } from '@env/environment';
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-my-profile-project-item',
|
||||
standalone: true,
|
||||
imports: [
|
||||
RouterLink
|
||||
],
|
||||
imports: [RouterLink],
|
||||
templateUrl: './my-profile-project-item.component.html',
|
||||
styleUrl: './my-profile-project-item.component.scss'
|
||||
styleUrl: './my-profile-project-item.component.scss',
|
||||
})
|
||||
export class MyProfileProjectItemComponent implements OnInit {
|
||||
|
||||
protected readonly environment = environment;
|
||||
@Input({required: true}) projectId: string = '';
|
||||
@Input({ required: true }) projectId = '';
|
||||
protected authService = inject(AuthService);
|
||||
|
||||
protected projectService = inject(ProjectService);
|
||||
|
||||
protected project: Project | undefined = undefined
|
||||
protected project: Project | undefined = undefined;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.projectService.getProjectById(this.projectId).subscribe(
|
||||
value => this.project = value
|
||||
);
|
||||
this.projectService.getProjectById(this.projectId).subscribe((value) => (this.project = value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,31 +4,29 @@
|
||||
|
||||
@if (projects) {
|
||||
<div class="relative flex items-center">
|
||||
<select [(ngModel)]="projectIdSelected"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]">
|
||||
<select
|
||||
[(ngModel)]="projectIdSelected"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"
|
||||
>
|
||||
<option [value]="null" disabled>Selectionner le projet à modifier</option>
|
||||
|
||||
<option [value]="'add'.toLowerCase()">
|
||||
Ajouter un nouveau projet
|
||||
</option>
|
||||
<option [value]="'add'.toLowerCase()">Ajouter un nouveau projet</option>
|
||||
|
||||
@for (project of projects; track project.id) {
|
||||
<option [value]="project.id">
|
||||
{{ project.nom }}
|
||||
</option>
|
||||
}
|
||||
|
||||
</select>
|
||||
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
|
||||
<div class="w-full my-8">
|
||||
@if (projectIdSelected() != null) {
|
||||
<app-my-profile-update-project-form [projectId]="projectIdSelected()"
|
||||
(formIsUpdated)="onProjectFormSubmitted($event)"/>
|
||||
<app-my-profile-update-project-form
|
||||
[projectId]="projectIdSelected()"
|
||||
(formIsUpdated)="onProjectFormSubmitted($event)"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,9 +8,8 @@ describe('MyProfileProjectListComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MyProfileProjectListComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
imports: [MyProfileProjectListComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MyProfileProjectListComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import {Component, inject, Input, OnInit, signal} from '@angular/core';
|
||||
import {
|
||||
MyProfileProjectItemComponent
|
||||
} from "@app/shared/components/my-profile-project-item/my-profile-project-item.component";
|
||||
import {PaginatorModule} from "primeng/paginator";
|
||||
import {ReactiveFormsModule} from "@angular/forms";
|
||||
import {ProjectService} from "@app/core/services/project/project.service";
|
||||
import {AsyncPipe, JsonPipe} from "@angular/common";
|
||||
import {Project} from "@app/shared/models/project";
|
||||
import {UntilDestroy} from "@ngneat/until-destroy";
|
||||
import {
|
||||
MyProfileUpdateProjectFormComponent
|
||||
} from "@app/shared/components/my-profile-update-project-form/my-profile-update-project-form.component";
|
||||
import { Component, inject, Input, OnInit, signal } from '@angular/core';
|
||||
import { MyProfileProjectItemComponent } from '@app/shared/components/my-profile-project-item/my-profile-project-item.component';
|
||||
import { PaginatorModule } from 'primeng/paginator';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { ProjectService } from '@app/core/services/project/project.service';
|
||||
import { AsyncPipe, JsonPipe } from '@angular/common';
|
||||
import { Project } from '@app/shared/models/project';
|
||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||
import { MyProfileUpdateProjectFormComponent } from '@app/shared/components/my-profile-update-project-form/my-profile-update-project-form.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-my-profile-project-list',
|
||||
@@ -21,28 +17,27 @@ import {
|
||||
ReactiveFormsModule,
|
||||
AsyncPipe,
|
||||
JsonPipe,
|
||||
MyProfileUpdateProjectFormComponent
|
||||
MyProfileUpdateProjectFormComponent,
|
||||
],
|
||||
templateUrl: './my-profile-project-list.component.html',
|
||||
styleUrl: './my-profile-project-list.component.scss'
|
||||
styleUrl: './my-profile-project-list.component.scss',
|
||||
})
|
||||
@UntilDestroy()
|
||||
export class MyProfileProjectListComponent implements OnInit {
|
||||
@Input({required: true}) projectIds: string[] = [];
|
||||
@Input({required: true}) userId: string = "";
|
||||
@Input({ required: true }) projectIds: string[] = [];
|
||||
@Input({ required: true }) userId = '';
|
||||
protected projectService = inject(ProjectService);
|
||||
protected projectIdSelected = signal<string|null>(null);
|
||||
protected projectIdSelected = signal<string | null>(null);
|
||||
|
||||
|
||||
protected projects: Project[] = []
|
||||
protected projects: Project[] = [];
|
||||
|
||||
ngOnInit(): void {
|
||||
this.projectService.getProjectByUserId(this.userId).subscribe(
|
||||
value => this.projects = value
|
||||
);
|
||||
this.projectService
|
||||
.getProjectByUserId(this.userId)
|
||||
.subscribe((value) => (this.projects = value));
|
||||
}
|
||||
|
||||
onProjectFormSubmitted($event: string | null) {
|
||||
this.projectIdSelected.set(null)
|
||||
this.projectIdSelected.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,64 @@
|
||||
<div class="flex max-sm:flex-col flex-row max-w-sm:space-y-2 space-x-2 justify-around items-center">
|
||||
@if (file !=null){
|
||||
@if (file != null) {
|
||||
<div class="flex-col flex space-y-2 justify-center items-center">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5 cursor-pointer text-red-600" (click)="file=null" >
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="w-5 h-5 cursor-pointer text-red-600"
|
||||
(click)="file = null"
|
||||
>
|
||||
<title>Supprimer le fichier</title>
|
||||
<path fill-rule="evenodd" d="M16.5 4.478v.227a48.816 48.816 0 0 1 3.878.512.75.75 0 1 1-.256 1.478l-.209-.035-1.005 13.07a3 3 0 0 1-2.991 2.77H8.084a3 3 0 0 1-2.991-2.77L4.087 6.66l-.209.035a.75.75 0 0 1-.256-1.478A48.567 48.567 0 0 1 7.5 4.705v-.227c0-1.564 1.213-2.9 2.816-2.951a52.662 52.662 0 0 1 3.369 0c1.603.051 2.815 1.387 2.815 2.951Zm-6.136-1.452a51.196 51.196 0 0 1 3.273 0C14.39 3.05 15 3.684 15 4.478v.113a49.488 49.488 0 0 0-6 0v-.113c0-.794.609-1.428 1.364-1.452Zm-.355 5.945a.75.75 0 1 0-1.5.058l.347 9a.75.75 0 1 0 1.499-.058l-.346-9Zm5.48.058a.75.75 0 1 0-1.498-.058l-.347 9a.75.75 0 0 0 1.5.058l.345-9Z" clip-rule="evenodd" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M16.5 4.478v.227a48.816 48.816 0 0 1 3.878.512.75.75 0 1 1-.256 1.478l-.209-.035-1.005 13.07a3 3 0 0 1-2.991 2.77H8.084a3 3 0 0 1-2.991-2.77L4.087 6.66l-.209.035a.75.75 0 0 1-.256-1.478A48.567 48.567 0 0 1 7.5 4.705v-.227c0-1.564 1.213-2.9 2.816-2.951a52.662 52.662 0 0 1 3.369 0c1.603.051 2.815 1.387 2.815 2.951Zm-6.136-1.452a51.196 51.196 0 0 1 3.273 0C14.39 3.05 15 3.684 15 4.478v.113a49.488 49.488 0 0 0-6 0v-.113c0-.794.609-1.428 1.364-1.452Zm-.355 5.945a.75.75 0 1 0-1.5.058l.347 9a.75.75 0 1 0 1.499-.058l-.346-9Zm5.48.058a.75.75 0 1 0-1.498-.058l-.347 9a.75.75 0 0 0 1.5.058l.345-9Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<img src="assets/images/pdf.svg" alt="pdf" class="max-w-sm max-h-16">
|
||||
<img src="assets/images/pdf.svg" alt="pdf" class="max-w-sm max-h-16" />
|
||||
|
||||
<small>{{file.name}}</small>
|
||||
<small>{{ file.name }}</small>
|
||||
</div>
|
||||
}
|
||||
|
||||
<label for="uploadFile1"
|
||||
class="flex justify-center items-center space-x-2 bg-gray-800 hover:bg-gray-700 text-white text-base px-3 py-1 outline-none rounded w-max cursor-pointer font-[sans-serif]">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 mr-2 fill-white inline" viewBox="0 0 32 32">
|
||||
<label
|
||||
for="uploadFile1"
|
||||
class="flex justify-center items-center space-x-2 bg-gray-800 hover:bg-gray-700 text-white text-base px-3 py-1 outline-none rounded w-max cursor-pointer font-[sans-serif]"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6 mr-2 fill-white inline"
|
||||
viewBox="0 0 32 32"
|
||||
>
|
||||
<path
|
||||
d="M23.75 11.044a7.99 7.99 0 0 0-15.5-.009A8 8 0 0 0 9 27h3a1 1 0 0 0 0-2H9a6 6 0 0 1-.035-12 1.038 1.038 0 0 0 1.1-.854 5.991 5.991 0 0 1 11.862 0A1.08 1.08 0 0 0 23 13a6 6 0 0 1 0 12h-3a1 1 0 0 0 0 2h3a8 8 0 0 0 .75-15.956z"
|
||||
data-original="#000000"/>
|
||||
data-original="#000000"
|
||||
/>
|
||||
<path
|
||||
d="M20.293 19.707a1 1 0 0 0 1.414-1.414l-5-5a1 1 0 0 0-1.414 0l-5 5a1 1 0 0 0 1.414 1.414L15 16.414V29a1 1 0 0 0 2 0V16.414z"
|
||||
data-original="#000000"/>
|
||||
data-original="#000000"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<small class="text-xs">Selectionner un fichier .pdf</small>
|
||||
<input type="file" id='uploadFile1' class="hidden"
|
||||
<input
|
||||
type="file"
|
||||
id="uploadFile1"
|
||||
class="hidden"
|
||||
accept="application/pdf"
|
||||
(change)="onFileChange($event)"/>
|
||||
(change)="onFileChange($event)"
|
||||
/>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
|
||||
@if (file != null) {
|
||||
<button type="button" [ngClass]="{'bg-purple-600':file!=null}"
|
||||
<button
|
||||
type="button"
|
||||
[ngClass]="{ 'bg-purple-600': file != null }"
|
||||
class="!mt-2 px-6 py-2 w-full bg-[#333] hover:bg-[#444] text-sm text-white mx-auto block"
|
||||
(click)="onSubmit()">Mettre à jour mon CV
|
||||
(click)="onSubmit()"
|
||||
>
|
||||
Mettre à jour mon CV
|
||||
</button>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MyProfileUpdateCvFormComponent } from './my-profile-update-cv-form.component';
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {ProfileService} from "@app/core/services/profile/profile.service";
|
||||
import {AuthService} from "@app/core/services/authentication/auth.service";
|
||||
import {signal} from "@angular/core";
|
||||
import {Auth} from "@app/shared/models/auth";
|
||||
import {provideRouter} from "@angular/router";
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { ProfileService } from '@app/core/services/profile/profile.service';
|
||||
import { AuthService } from '@app/core/services/authentication/auth.service';
|
||||
import { signal } from '@angular/core';
|
||||
import { Auth } from '@app/shared/models/auth';
|
||||
import { provideRouter } from '@angular/router';
|
||||
|
||||
describe('MyProfileUpdateCvFormComponent', () => {
|
||||
let component: MyProfileUpdateCvFormComponent;
|
||||
@@ -17,22 +17,21 @@ describe('MyProfileUpdateCvFormComponent', () => {
|
||||
let mockAuthService: Partial<AuthService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
mockToastrService={
|
||||
mockToastrService = {
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
}
|
||||
};
|
||||
|
||||
mockProfileService = {
|
||||
updateProfile: jest.fn().mockReturnValue({
|
||||
subscribe: jest.fn()
|
||||
})
|
||||
subscribe: jest.fn(),
|
||||
}),
|
||||
};
|
||||
|
||||
mockAuthService = {
|
||||
updateUser: jest.fn(),
|
||||
user: signal<Auth|undefined>(undefined)
|
||||
user: signal<Auth | undefined>(undefined),
|
||||
};
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
@@ -41,10 +40,9 @@ describe('MyProfileUpdateCvFormComponent', () => {
|
||||
provideRouter([]),
|
||||
{ provide: ToastrService, useValue: mockToastrService },
|
||||
{ provide: ProfileService, useValue: mockProfileService },
|
||||
{ provide: AuthService, useValue: mockAuthService }
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
{ provide: AuthService, useValue: mockAuthService },
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MyProfileUpdateCvFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
import {Component, inject, Input, output} from '@angular/core';
|
||||
import {AuthService} from "@app/core/services/authentication/auth.service";
|
||||
import {Profile} from "@app/shared/models/profile";
|
||||
import {ProfileService} from "@app/core/services/profile/profile.service";
|
||||
import {NgClass} from "@angular/common";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import { Component, inject, Input, output } from '@angular/core';
|
||||
import { AuthService } from '@app/core/services/authentication/auth.service';
|
||||
import { Profile } from '@app/shared/models/profile';
|
||||
import { ProfileService } from '@app/core/services/profile/profile.service';
|
||||
import { NgClass } from '@angular/common';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
|
||||
@Component({
|
||||
selector: 'app-my-profile-update-cv-form',
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgClass
|
||||
],
|
||||
imports: [NgClass],
|
||||
templateUrl: './my-profile-update-cv-form.component.html',
|
||||
styleUrl: './my-profile-update-cv-form.component.scss'
|
||||
styleUrl: './my-profile-update-cv-form.component.scss',
|
||||
})
|
||||
export class MyProfileUpdateCvFormComponent {
|
||||
|
||||
@Input({required: true}) profile: Profile | undefined = undefined;
|
||||
@Input({ required: true }) profile: Profile | undefined = undefined;
|
||||
|
||||
private readonly profileService = inject(ProfileService);
|
||||
private readonly toastrService = inject(ToastrService);
|
||||
@@ -26,37 +23,26 @@ export class MyProfileUpdateCvFormComponent {
|
||||
file: File | null = null; // Variable to store file
|
||||
|
||||
onSubmit() {
|
||||
|
||||
if (this.file != null) {
|
||||
const formData = new FormData();
|
||||
formData.append('cv', this.file); // "avatar" est le nom du champ dans PocketBase
|
||||
|
||||
this.profileService.updateProfile(this.profile?.id!, formData).subscribe(
|
||||
value => {
|
||||
this.profileService.updateProfile(this.profile?.id!, formData).subscribe((value) => {
|
||||
this.authService.updateUser();
|
||||
|
||||
this.toastrService.success(
|
||||
` Votre CV a bien été modifier !`,
|
||||
`Mise à jour`,
|
||||
{
|
||||
this.toastrService.success(` Votre CV a bien été modifier !`, `Mise à jour`, {
|
||||
closeButton: true,
|
||||
progressAnimation: 'decreasing',
|
||||
progressBar: true
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
progressBar: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onFileChange($event: Event) {
|
||||
const target: HTMLInputElement = $event.target as HTMLInputElement;
|
||||
if (target?.files?.[0]) {
|
||||
this.file = target.files[0];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,27 +1,38 @@
|
||||
<form
|
||||
class="space-y-6 font-[sans-serif] max-w-md"
|
||||
[formGroup]="profileForm"
|
||||
(ngSubmit)="onSubmit()"
|
||||
>
|
||||
<h3 class="font-ubuntu font-bold text-xl uppercase dark:text-white mb-4">
|
||||
Mon curriculum vitae (CV)
|
||||
</h3>
|
||||
<app-my-profile-update-cv-form [profile]="profile" />
|
||||
|
||||
<form class="space-y-6 font-[sans-serif] max-w-md " [formGroup]="profileForm" (ngSubmit)="onSubmit()">
|
||||
|
||||
<h3 class="font-ubuntu font-bold text-xl uppercase dark:text-white mb-4">Mon curriculum vitae (CV)</h3>
|
||||
<app-my-profile-update-cv-form [profile]="profile"/>
|
||||
|
||||
|
||||
<h3 class="font-ubuntu font-bold text-xl uppercase dark:text-white">Ce qu'il faut savoir de moi</h3>
|
||||
<h3 class="font-ubuntu font-bold text-xl uppercase dark:text-white">
|
||||
Ce qu'il faut savoir de moi
|
||||
</h3>
|
||||
<div class="mx-8">
|
||||
<div>
|
||||
<label class="mb-2 text-sm text-black block dark:text-white">Biographie</label>
|
||||
<div class="relative flex items-center">
|
||||
<textarea placeholder='Type Message' formControlName="bio"
|
||||
<textarea
|
||||
placeholder="Type Message"
|
||||
formControlName="bio"
|
||||
class="p-4 bg-white max-w-md mx-auto w-full block text-sm border border-gray-300 outline-[#007bff] rounded"
|
||||
rows="4"></textarea>
|
||||
rows="4"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-2 text-sm text-black block dark:text-white">A propos de vous</label>
|
||||
<div class="relative flex items-center">
|
||||
<textarea placeholder='Type Message' formControlName="apropos"
|
||||
<textarea
|
||||
placeholder="Type Message"
|
||||
formControlName="apropos"
|
||||
class="p-4 bg-white max-w-md mx-auto w-full block text-sm border border-gray-300 outline-[#007bff] rounded"
|
||||
rows="4"></textarea>
|
||||
rows="4"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,19 +40,32 @@
|
||||
<h3 class="font-ubuntu font-bold text-xl uppercase dark:text-white">Mon domaine de competence</h3>
|
||||
|
||||
<div class="mx-8">
|
||||
|
||||
<div>
|
||||
<label class="mb-2 text-sm text-black block dark:text-white">Profession</label>
|
||||
<div class="relative flex items-center">
|
||||
<input type='text' placeholder='votre metier' formControlName="profession"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="votre metier"
|
||||
formControlName="profession"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"
|
||||
/>
|
||||
|
||||
<div class="absolute left-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
|
||||
<path fill-rule="evenodd" d="M11 4V3a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v1H4a2 2 0 0 0-2 2v3a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-1ZM9 2.5H7a.5.5 0 0 0-.5.5v1h3V3a.5.5 0 0 0-.5-.5ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" clip-rule="evenodd" />
|
||||
<path d="M3 11.83V12a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2v-.17c-.313.11-.65.17-1 .17H4c-.35 0-.687-.06-1-.17Z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="size-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M11 4V3a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v1H4a2 2 0 0 0-2 2v3a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-1ZM9 2.5H7a.5.5 0 0 0-.5.5v1h3V3a.5.5 0 0 0-.5-.5ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="M3 11.83V12a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2v-.17c-.313.11-.65.17-1 .17H4c-.35 0-.687-.06-1-.17Z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -50,32 +74,43 @@
|
||||
<label class="mb-2 text-sm text-black block dark:text-white">Secteur</label>
|
||||
|
||||
<div class="relative flex items-center">
|
||||
<select formControlName="secteur" class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]">
|
||||
<select
|
||||
formControlName="secteur"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"
|
||||
>
|
||||
<option [ngValue]="null" disabled>Selectionner votre secteur d'activité</option>
|
||||
@for (sector of sectors(); track sector.id) {
|
||||
<option [value]="sector.id">{{ sector.nom }}</option>
|
||||
}
|
||||
</select>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<h3 class="font-ubuntu font-bold text-xl uppercase dark:text-white" >Mes réseaux </h3>
|
||||
<h3 class="font-ubuntu font-bold text-xl uppercase dark:text-white">Mes réseaux</h3>
|
||||
<div formGroupName="reseaux" class="mx-8">
|
||||
<div>
|
||||
<label class="mb-2 text-sm text-black block dark:text-white uppercase">Facebook</label>
|
||||
<div class="relative flex items-center">
|
||||
<input type='text' placeholder='lien vers votre facebook' formControlName="facebook"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="lien vers votre facebook"
|
||||
formControlName="facebook"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"
|
||||
/>
|
||||
|
||||
<div class="absolute left-4">
|
||||
<svg class="w-6 h-6" fill="#0866FF" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
class="w-6 h-6"
|
||||
fill="#0866FF"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>Facebook</title>
|
||||
<path
|
||||
d="M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z"/>
|
||||
d="M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
@@ -83,31 +118,51 @@
|
||||
<div>
|
||||
<label class="mb-2 text-sm text-black block dark:text-white uppercase">Github</label>
|
||||
<div class="relative flex items-center">
|
||||
<input type='text' placeholder='Lien vers votre github' formControlName="github"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Lien vers votre github"
|
||||
formControlName="github"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"
|
||||
/>
|
||||
|
||||
<div class="absolute left-4">
|
||||
<svg class="w-6 h-6" fill="#181717" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>
|
||||
GitHub</title>
|
||||
<svg
|
||||
class="w-6 h-6"
|
||||
fill="#181717"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>GitHub</title>
|
||||
<path
|
||||
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/>
|
||||
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 text-sm text-black block dark:text-white uppercase">Instagram</label>
|
||||
<div class="relative flex items-center">
|
||||
<input type='text' placeholder='Lien vers votre instagram' formControlName="instagram"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Lien vers votre instagram"
|
||||
formControlName="instagram"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"
|
||||
/>
|
||||
|
||||
<div class="absolute left-4">
|
||||
<svg class="w-6 h-6" fill="#E4405F" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
class="w-6 h-6"
|
||||
fill="#E4405F"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>Instagram</title>
|
||||
<path
|
||||
d="M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077"/>
|
||||
d="M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
@@ -115,14 +170,25 @@
|
||||
<div>
|
||||
<label class="mb-2 text-sm text-black block dark:text-white uppercase">linkedIn</label>
|
||||
<div class="relative flex items-center">
|
||||
<input type='text' placeholder='Lien vers votre linkedIn' formControlName="linkedIn"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Lien vers votre linkedIn"
|
||||
formControlName="linkedIn"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"
|
||||
/>
|
||||
|
||||
<div class="absolute left-4">
|
||||
<svg class="w-6 h-6" fill="#0A66C2" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
class="w-6 h-6"
|
||||
fill="#0A66C2"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>LinkedIn</title>
|
||||
<path
|
||||
d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
|
||||
d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
@@ -130,54 +196,88 @@
|
||||
<div>
|
||||
<label class="mb-2 text-sm text-black block dark:text-white uppercase">web</label>
|
||||
<div class="relative flex items-center">
|
||||
<input type='text' placeholder='Lien vers votre site web' formControlName="web"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Lien vers votre site web"
|
||||
formControlName="web"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"
|
||||
/>
|
||||
|
||||
<div class="absolute left-4">
|
||||
<svg id="Layer_1" class="w-6 h-6" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"><title>world-globe-outline</title>
|
||||
<svg
|
||||
id="Layer_1"
|
||||
class="w-6 h-6"
|
||||
data-name="Layer 1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<title>world-globe-outline</title>
|
||||
<path
|
||||
d="M256,0Q362.11,0,436.9,75.1,512,149.89,512,256T436.9,436.9Q362.11,512,256,512T75.1,436.9Q0,362.11,0,256T75.1,75.1Q149.89,0,256,0ZM55.34,190.63a211.82,211.82,0,0,0,0,130.73h66a416,416,0,0,1,0-130.73Zm14.9,165.7q34.36,63.55,100.64,92.73a232.64,232.64,0,0,1-20.07-31.92,302.59,302.59,0,0,1-22.2-60.81ZM170.87,63.24Q104.59,92.43,70.54,155.37h58.07a304.36,304.36,0,0,1,22.2-60.51A198.45,198.45,0,0,1,170.87,63.24ZM151.72,190.63A390.59,390.59,0,0,0,145.94,256a390.48,390.48,0,0,0,5.78,65.37H241.1V190.63Zm82.7-143.51q-32.83,13.38-57.16,61.11-9.43,19.16-17.63,47.13H241.1V45.61a3.4,3.4,0,0,1-1.52.3h-1.82Zm3.34,419h1.82a5.12,5.12,0,0,0,1.52.3V356.33H159.62q8.21,28.28,17.63,47.43,24.32,47.74,57.16,61.11ZM274.24,45.91h-1.83a3.38,3.38,0,0,1-1.52-.3V155.37h81.48q-8.21-28-17.63-47.13-24.33-47.73-57.16-61.11Zm86,275.46A391.23,391.23,0,0,0,366.06,256a395,395,0,0,0-5.78-65.37H270.9V321.37Zm-82.7,143.51q32.84-13.39,57.16-61.11,9.73-19.16,17.63-47.43H270.9V466.39a5.1,5.1,0,0,0,1.52-.3h1.83ZM441.46,155.37q-34.06-62.94-100-92.12A212.61,212.61,0,0,1,361.2,94.86a295.22,295.22,0,0,1,22.2,60.51Zm-100,293.7q66-29.49,100-92.73H383.39q-8.52,33.74-22.2,60.81A226,226,0,0,1,341.43,449.06Zm49.25-258.43A412,412,0,0,1,395.86,256a415.71,415.71,0,0,1-5.17,65.37h66a211.89,211.89,0,0,0,0-130.73Z"/>
|
||||
d="M256,0Q362.11,0,436.9,75.1,512,149.89,512,256T436.9,436.9Q362.11,512,256,512T75.1,436.9Q0,362.11,0,256T75.1,75.1Q149.89,0,256,0ZM55.34,190.63a211.82,211.82,0,0,0,0,130.73h66a416,416,0,0,1,0-130.73Zm14.9,165.7q34.36,63.55,100.64,92.73a232.64,232.64,0,0,1-20.07-31.92,302.59,302.59,0,0,1-22.2-60.81ZM170.87,63.24Q104.59,92.43,70.54,155.37h58.07a304.36,304.36,0,0,1,22.2-60.51A198.45,198.45,0,0,1,170.87,63.24ZM151.72,190.63A390.59,390.59,0,0,0,145.94,256a390.48,390.48,0,0,0,5.78,65.37H241.1V190.63Zm82.7-143.51q-32.83,13.38-57.16,61.11-9.43,19.16-17.63,47.13H241.1V45.61a3.4,3.4,0,0,1-1.52.3h-1.82Zm3.34,419h1.82a5.12,5.12,0,0,0,1.52.3V356.33H159.62q8.21,28.28,17.63,47.43,24.32,47.74,57.16,61.11ZM274.24,45.91h-1.83a3.38,3.38,0,0,1-1.52-.3V155.37h81.48q-8.21-28-17.63-47.13-24.33-47.73-57.16-61.11Zm86,275.46A391.23,391.23,0,0,0,366.06,256a395,395,0,0,0-5.78-65.37H270.9V321.37Zm-82.7,143.51q32.84-13.39,57.16-61.11,9.73-19.16,17.63-47.43H270.9V466.39a5.1,5.1,0,0,0,1.52-.3h1.83ZM441.46,155.37q-34.06-62.94-100-92.12A212.61,212.61,0,0,1,361.2,94.86a295.22,295.22,0,0,1,22.2,60.51Zm-100,293.7q66-29.49,100-92.73H383.39q-8.52,33.74-22.2,60.81A226,226,0,0,1,341.43,449.06Zm49.25-258.43A412,412,0,0,1,395.86,256a415.71,415.71,0,0,1-5.17,65.37h66a211.89,211.89,0,0,0,0-130.73Z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 text-sm text-black block dark:text-white uppercase">x</label>
|
||||
<div class="relative flex items-center">
|
||||
<input type='text' placeholder='Lien vers votre compte X' formControlName="x"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Lien vers votre compte X"
|
||||
formControlName="x"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"
|
||||
/>
|
||||
|
||||
<div class="absolute left-4">
|
||||
|
||||
<svg class="w-6 h-6" fill="#000000" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
class="w-6 h-6"
|
||||
fill="#000000"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>X</title>
|
||||
<path
|
||||
d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z"/>
|
||||
d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 text-sm text-black block dark:text-white uppercase">YouTube</label>
|
||||
<div class="relative flex items-center">
|
||||
<input type='text' placeholder='Lien vers votre compte youtube' formControlName="youTube"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Lien vers votre compte youtube"
|
||||
formControlName="youTube"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"
|
||||
/>
|
||||
|
||||
<div class="absolute left-4">
|
||||
<svg class="w-6 h-6" fill="#FF0000" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
class="w-6 h-6"
|
||||
fill="#FF0000"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>YouTube</title>
|
||||
<path
|
||||
d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/>
|
||||
d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" [ngClass]="{'bg-purple-600':profileForm.valid}"
|
||||
class="!mt-8 px-6 py-2 w-full bg-[#333] hover:bg-[#444] text-sm text-white mx-auto block">Sauvegarder
|
||||
<button
|
||||
type="submit"
|
||||
[ngClass]="{ 'bg-purple-600': profileForm.valid }"
|
||||
class="!mt-8 px-6 py-2 w-full bg-[#333] hover:bg-[#444] text-sm text-white mx-auto block"
|
||||
>
|
||||
Sauvegarder
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import {MyProfileUpdateFormComponent} from './my-profile-update-form.component';
|
||||
import {provideRouter} from "@angular/router";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {FormBuilder} from "@angular/forms";
|
||||
import { MyProfileUpdateFormComponent } from './my-profile-update-form.component';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
|
||||
describe('MyProfileUpdateFormComponent', () => {
|
||||
let component: MyProfileUpdateFormComponent;
|
||||
let fixture: ComponentFixture<MyProfileUpdateFormComponent>;
|
||||
|
||||
let mockToastrService : Partial<ToastrService>;
|
||||
let mockToastrService: Partial<ToastrService>;
|
||||
|
||||
let mockProfileData = {profession:'',secteur:'',bio:'',apropos:'',reseaux:{facebook:'',github:'',instagram:'',linkedIn:'',web:'',x:'',youTube:''}};
|
||||
const mockProfileData = {
|
||||
profession: '',
|
||||
secteur: '',
|
||||
bio: '',
|
||||
apropos: '',
|
||||
reseaux: { facebook: '', github: '', instagram: '', linkedIn: '', web: '', x: '', youTube: '' },
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
mockToastrService = {
|
||||
warning: jest.fn(),
|
||||
success: jest.fn(),
|
||||
error: jest.fn()
|
||||
error: jest.fn(),
|
||||
};
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
@@ -26,10 +31,9 @@ describe('MyProfileUpdateFormComponent', () => {
|
||||
providers: [
|
||||
FormBuilder,
|
||||
provideRouter([]),
|
||||
{ provide: ToastrService, useValue: mockToastrService }
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
{ provide: ToastrService, useValue: mockToastrService },
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MyProfileUpdateFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -1,34 +1,33 @@
|
||||
import {Component, inject, Input, OnInit, signal} from '@angular/core';
|
||||
import {FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms";
|
||||
import {UntilDestroy} from "@ngneat/until-destroy";
|
||||
import {Profile} from "@app/shared/models/profile";
|
||||
import {NgClass} from "@angular/common";
|
||||
import {SectorService} from "@app/core/services/sector/sector.service";
|
||||
import {Sector} from "@app/shared/models/sector";
|
||||
import {ProfileService} from "@app/core/services/profile/profile.service";
|
||||
import {AuthService} from "@app/core/services/authentication/auth.service";
|
||||
import { Component, inject, Input, OnInit, signal } from '@angular/core';
|
||||
import {
|
||||
MyProfileUpdateCvFormComponent
|
||||
} from "@app/shared/components/my-profile-update-cv-form/my-profile-update-cv-form.component";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
FormBuilder,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
ReactiveFormsModule,
|
||||
Validators,
|
||||
} from '@angular/forms';
|
||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||
import { Profile } from '@app/shared/models/profile';
|
||||
import { NgClass } from '@angular/common';
|
||||
import { SectorService } from '@app/core/services/sector/sector.service';
|
||||
import { Sector } from '@app/shared/models/sector';
|
||||
import { ProfileService } from '@app/core/services/profile/profile.service';
|
||||
import { AuthService } from '@app/core/services/authentication/auth.service';
|
||||
import { MyProfileUpdateCvFormComponent } from '@app/shared/components/my-profile-update-cv-form/my-profile-update-cv-form.component';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
|
||||
@Component({
|
||||
selector: 'app-my-profile-update-form',
|
||||
standalone: true,
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
NgClass,
|
||||
MyProfileUpdateCvFormComponent
|
||||
],
|
||||
imports: [ReactiveFormsModule, NgClass, MyProfileUpdateCvFormComponent],
|
||||
templateUrl: './my-profile-update-form.component.html',
|
||||
styleUrl: './my-profile-update-form.component.scss'
|
||||
styleUrl: './my-profile-update-form.component.scss',
|
||||
})
|
||||
@UntilDestroy()
|
||||
export class MyProfileUpdateFormComponent implements OnInit {
|
||||
|
||||
private readonly toastrService = inject(ToastrService);
|
||||
|
||||
@Input({required: true}) profile: Profile = {} as Profile;
|
||||
@Input({ required: true }) profile: Profile = {} as Profile;
|
||||
private readonly formBuilder = inject(FormBuilder);
|
||||
protected readonly sectorService = inject(SectorService);
|
||||
protected readonly profileService = inject(ProfileService);
|
||||
@@ -36,33 +35,48 @@ export class MyProfileUpdateFormComponent implements OnInit {
|
||||
profileForm!: FormGroup;
|
||||
protected sectors = signal<Sector[]>([]);
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
this.profileForm = this.formBuilder.group({
|
||||
profession: new FormControl(this.profile.profession?this.profile.profession.toLowerCase():'', [Validators.required]),
|
||||
secteur: new FormControl<string|null>(this.profile.secteur ? this.profile.secteur.toLowerCase() : null, [Validators.required]),
|
||||
profession: new FormControl(
|
||||
this.profile.profession ? this.profile.profession.toLowerCase() : '',
|
||||
[Validators.required]
|
||||
),
|
||||
secteur: new FormControl<string | null>(
|
||||
this.profile.secteur ? this.profile.secteur.toLowerCase() : null,
|
||||
[Validators.required]
|
||||
),
|
||||
bio: new FormControl(this.profile.bio ? this.profile.bio.toLowerCase() : ''),
|
||||
apropos: new FormControl(this.profile.apropos ? this.profile.apropos.toLowerCase() : ''),
|
||||
reseaux: new FormGroup({
|
||||
facebook: new FormControl(this.profile.reseaux ? (this.profile.reseaux as any)["facebook"] : ''),
|
||||
github: new FormControl(this.profile.reseaux ?(this.profile.reseaux as any)["github"] : ''),
|
||||
instagram: new FormControl(this.profile.reseaux ? (this.profile.reseaux as any)["instagram"] : ''),
|
||||
linkedIn: new FormControl(this.profile.reseaux ? (this.profile.reseaux as any)["linkedIn"] : ''),
|
||||
web: new FormControl(this.profile.reseaux ? (this.profile.reseaux as any)["web"]: ''),
|
||||
x: new FormControl(this.profile.reseaux ? (this.profile.reseaux as any)["x"] : ''),
|
||||
youTube: new FormControl(this.profile.reseaux ? (this.profile.reseaux as any)["youTube"] : '')
|
||||
})
|
||||
facebook: new FormControl(
|
||||
this.profile.reseaux ? (this.profile.reseaux as any)['facebook'] : ''
|
||||
),
|
||||
github: new FormControl(
|
||||
this.profile.reseaux ? (this.profile.reseaux as any)['github'] : ''
|
||||
),
|
||||
instagram: new FormControl(
|
||||
this.profile.reseaux ? (this.profile.reseaux as any)['instagram'] : ''
|
||||
),
|
||||
linkedIn: new FormControl(
|
||||
this.profile.reseaux ? (this.profile.reseaux as any)['linkedIn'] : ''
|
||||
),
|
||||
web: new FormControl(this.profile.reseaux ? (this.profile.reseaux as any)['web'] : ''),
|
||||
x: new FormControl(this.profile.reseaux ? (this.profile.reseaux as any)['x'] : ''),
|
||||
youTube: new FormControl(
|
||||
this.profile.reseaux ? (this.profile.reseaux as any)['youTube'] : ''
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
if (this.profile.secteur) {
|
||||
this.sectorService.getSectorById(this.profile.secteur).subscribe(value => this.profileForm.get("secteur")!.setValue(value.id));
|
||||
this.sectorService
|
||||
.getSectorById(this.profile.secteur)
|
||||
.subscribe((value) => this.profileForm.get('secteur')!.setValue(value.id));
|
||||
}
|
||||
|
||||
this.sectorService.sectors.subscribe(value => this.sectors.set(value));
|
||||
|
||||
this.sectorService.sectors.subscribe((value) => this.sectors.set(value));
|
||||
}
|
||||
|
||||
|
||||
onSubmit() {
|
||||
if (this.profileForm.invalid) {
|
||||
return;
|
||||
@@ -73,8 +87,8 @@ export class MyProfileUpdateFormComponent implements OnInit {
|
||||
secteur: this.profileForm.getRawValue().secteur,
|
||||
apropos: this.profileForm.getRawValue().apropos,
|
||||
bio: this.profileForm.getRawValue().bio,
|
||||
reseaux: this.profileForm.getRawValue().reseaux
|
||||
} as Profile
|
||||
reseaux: this.profileForm.getRawValue().reseaux,
|
||||
} as Profile;
|
||||
|
||||
this.profileService.updateProfile(this.profile.id, data).subscribe({
|
||||
next: (value) => {
|
||||
@@ -86,16 +100,16 @@ export class MyProfileUpdateFormComponent implements OnInit {
|
||||
{
|
||||
closeButton: true,
|
||||
progressAnimation: 'decreasing',
|
||||
progressBar: true
|
||||
progressBar: true,
|
||||
}
|
||||
);
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastrService.error("Une erreur est survenue lors de la mise à jour de votre profil", "Erreur");
|
||||
}
|
||||
}
|
||||
this.toastrService.error(
|
||||
'Une erreur est survenue lors de la mise à jour de votre profil',
|
||||
'Erreur'
|
||||
);
|
||||
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,62 +1,89 @@
|
||||
@if (projectId) {
|
||||
|
||||
@if (projectId == 'add'.toLowerCase()) {
|
||||
<app-project-picture-form [project]="undefined"/>
|
||||
<app-project-picture-form [project]="undefined" />
|
||||
} @else {
|
||||
<app-project-picture-form [project]="project"/>
|
||||
<app-project-picture-form [project]="project" />
|
||||
}
|
||||
|
||||
<h3 class="font-ubuntu w-full text-start font-bold text-xl uppercase dark:text-white my-5">Information du
|
||||
projet </h3>
|
||||
<h3 class="font-ubuntu w-full text-start font-bold text-xl uppercase dark:text-white my-5">
|
||||
Information du projet
|
||||
</h3>
|
||||
|
||||
<form class="mx-8" [formGroup]="projectForm" (ngSubmit)="onSubmit()">
|
||||
|
||||
<label class="mb-2 text-sm text-black block dark:text-white">Nom</label>
|
||||
<div class="relative flex items-center">
|
||||
<input type='text' placeholder='nom du projet' formControlName="nom"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="nom du projet"
|
||||
formControlName="nom"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"
|
||||
/>
|
||||
|
||||
<div class="absolute left-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22px" height="22px" fill="#bbb" viewBox="0 0 512 512">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="22px"
|
||||
height="22px"
|
||||
fill="#bbb"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
d="M437.02 74.981C388.667 26.629 324.38 0 256 0S123.333 26.629 74.98 74.981C26.629 123.333 0 187.62 0 256s26.629 132.667 74.98 181.019C123.333 485.371 187.62 512 256 512s132.667-26.629 181.02-74.981C485.371 388.667 512 324.38 512 256s-26.629-132.667-74.98-181.019zM256 482c-66.869 0-127.037-29.202-168.452-75.511C113.223 338.422 178.948 290 256 290c-49.706 0-90-40.294-90-90s40.294-90 90-90 90 40.294 90 90-40.294 90-90 90c77.052 0 142.777 48.422 168.452 116.489C383.037 452.798 322.869 482 256 482z"
|
||||
data-original="#000000"></path>
|
||||
data-original="#000000"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<label class="mb-2 text-sm text-black block dark:text-white">Lien</label>
|
||||
<div class="relative flex items-center">
|
||||
<input type='text' placeholder='lien vers votre projet ex : http://monprojet' formControlName="lien"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="lien vers votre projet ex : http://monprojet"
|
||||
formControlName="lien"
|
||||
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"
|
||||
/>
|
||||
|
||||
<div class="absolute left-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22px" height="22px" fill="#bbb" viewBox="0 0 512 512">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="22px"
|
||||
height="22px"
|
||||
fill="#bbb"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
d="M437.02 74.981C388.667 26.629 324.38 0 256 0S123.333 26.629 74.98 74.981C26.629 123.333 0 187.62 0 256s26.629 132.667 74.98 181.019C123.333 485.371 187.62 512 256 512s132.667-26.629 181.02-74.981C485.371 388.667 512 324.38 512 256s-26.629-132.667-74.98-181.019zM256 482c-66.869 0-127.037-29.202-168.452-75.511C113.223 338.422 178.948 290 256 290c-49.706 0-90-40.294-90-90s40.294-90 90-90 90 40.294 90 90-40.294 90-90 90c77.052 0 142.777 48.422 168.452 116.489C383.037 452.798 322.869 482 256 482z"
|
||||
data-original="#000000"></path>
|
||||
data-original="#000000"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="mb-2 text-sm text-black block dark:text-white">description</label>
|
||||
<div class="relative flex items-center">
|
||||
<textarea placeholder='Type Message' formControlName="description"
|
||||
<textarea
|
||||
placeholder="Type Message"
|
||||
formControlName="description"
|
||||
class="p-4 bg-white w-full block text-sm border border-gray-300 outline-[#007bff] rounded"
|
||||
rows="4"></textarea>
|
||||
rows="4"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
|
||||
<button type="submit" [ngClass]="{'bg-purple-600':projectForm.valid}"
|
||||
class="!mt-8 px-6 py-2 w-full bg-[#333] hover:bg-[#444] text-sm text-white mx-auto block">Sauvegarder
|
||||
<button
|
||||
type="submit"
|
||||
[ngClass]="{ 'bg-purple-600': projectForm.valid }"
|
||||
class="!mt-8 px-6 py-2 w-full bg-[#333] hover:bg-[#444] text-sm text-white mx-auto block"
|
||||
>
|
||||
Sauvegarder
|
||||
</button>
|
||||
|
||||
<button type="button" (click)="formIsUpdated.emit(null)"
|
||||
class="!mt-8 px-6 py-2 w-full bg-[#333] hover:bg-[#444] text-sm text-white mx-auto block">Annuler
|
||||
<button
|
||||
type="button"
|
||||
(click)="formIsUpdated.emit(null)"
|
||||
class="!mt-8 px-6 py-2 w-full bg-[#333] hover:bg-[#444] text-sm text-white mx-auto block"
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
|
||||
</form>
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MyProfileUpdateProjectFormComponent } from './my-profile-update-project-form.component';
|
||||
import {AuthService} from "@app/core/services/authentication/auth.service";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {ProjectService} from "@app/core/services/project/project.service";
|
||||
import {provideRouter} from "@angular/router";
|
||||
import {signal} from "@angular/core";
|
||||
import {Auth} from "@app/shared/models/auth";
|
||||
import { AuthService } from '@app/core/services/authentication/auth.service';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { ProjectService } from '@app/core/services/project/project.service';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { signal } from '@angular/core';
|
||||
import { Auth } from '@app/shared/models/auth';
|
||||
|
||||
describe('MyProfileUpdateProjectFormComponent', () => {
|
||||
let component: MyProfileUpdateProjectFormComponent;
|
||||
@@ -17,18 +17,17 @@ describe('MyProfileUpdateProjectFormComponent', () => {
|
||||
let mockToastrService: Partial<ToastrService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
mockToastrService={
|
||||
mockToastrService = {
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
warning: jest.fn()
|
||||
}
|
||||
|
||||
mockAuthService = {
|
||||
user: signal<Auth|undefined>(undefined),
|
||||
warning: jest.fn(),
|
||||
};
|
||||
|
||||
mockProjectService={}
|
||||
mockAuthService = {
|
||||
user: signal<Auth | undefined>(undefined),
|
||||
};
|
||||
|
||||
mockProjectService = {};
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MyProfileUpdateProjectFormComponent],
|
||||
@@ -36,10 +35,9 @@ describe('MyProfileUpdateProjectFormComponent', () => {
|
||||
provideRouter([]),
|
||||
{ provide: AuthService, useValue: mockAuthService },
|
||||
{ provide: ToastrService, useValue: mockToastrService },
|
||||
{ provide: ProjectService, useValue: mockProjectService }
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
{ provide: ProjectService, useValue: mockProjectService },
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MyProfileUpdateProjectFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -1,31 +1,25 @@
|
||||
import {Component, inject, Input, OnChanges, OnInit, output, SimpleChanges} from '@angular/core';
|
||||
import {FormBuilder, FormControl, ReactiveFormsModule, Validators} from "@angular/forms";
|
||||
import {ProjectService} from "@app/core/services/project/project.service";
|
||||
import {Project} from "@app/shared/models/project";
|
||||
import {NgClass} from "@angular/common";
|
||||
import {PaginatorModule} from "primeng/paginator";
|
||||
import {ProjectPictureFormComponent} from "@app/shared/components/project-picture-form/project-picture-form.component";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {AuthService} from "@app/core/services/authentication/auth.service";
|
||||
import {ProjectDto} from "@app/shared/models/project-dto";
|
||||
import { Component, inject, Input, OnChanges, OnInit, output, SimpleChanges } from '@angular/core';
|
||||
import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { ProjectService } from '@app/core/services/project/project.service';
|
||||
import { Project } from '@app/shared/models/project';
|
||||
import { NgClass } from '@angular/common';
|
||||
import { PaginatorModule } from 'primeng/paginator';
|
||||
import { ProjectPictureFormComponent } from '@app/shared/components/project-picture-form/project-picture-form.component';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { AuthService } from '@app/core/services/authentication/auth.service';
|
||||
import { ProjectDto } from '@app/shared/models/project-dto';
|
||||
|
||||
@Component({
|
||||
selector: 'app-my-profile-update-project-form',
|
||||
standalone: true,
|
||||
imports: [
|
||||
PaginatorModule,
|
||||
ReactiveFormsModule,
|
||||
NgClass,
|
||||
ProjectPictureFormComponent
|
||||
],
|
||||
imports: [PaginatorModule, ReactiveFormsModule, NgClass, ProjectPictureFormComponent],
|
||||
templateUrl: './my-profile-update-project-form.component.html',
|
||||
styleUrl: './my-profile-update-project-form.component.scss'
|
||||
styleUrl: './my-profile-update-project-form.component.scss',
|
||||
})
|
||||
export class MyProfileUpdateProjectFormComponent implements OnInit, OnChanges {
|
||||
@Input({ required: true }) projectId: string | null = null;
|
||||
|
||||
@Input({required: true}) projectId: string | null = null;
|
||||
|
||||
protected project: Project | undefined = undefined
|
||||
protected project: Project | undefined = undefined;
|
||||
private readonly toastrService = inject(ToastrService);
|
||||
private readonly authService = inject(AuthService);
|
||||
|
||||
@@ -35,37 +29,30 @@ export class MyProfileUpdateProjectFormComponent implements OnInit, OnChanges {
|
||||
protected projectForm = this.formBuilder.group({
|
||||
nom: new FormControl('', [Validators.required]),
|
||||
description: new FormControl('', [Validators.required]),
|
||||
lien: new FormControl('')
|
||||
lien: new FormControl(''),
|
||||
});
|
||||
|
||||
formIsUpdated = output<string | null>({alias: 'formIsUpdated'})
|
||||
|
||||
formIsUpdated = output<string | null>();
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.projectId == 'add'.toLowerCase()){
|
||||
if (this.projectId == 'add'.toLowerCase()) {
|
||||
this.projectForm.setValue({
|
||||
nom: '',
|
||||
description: '',
|
||||
lien: ''
|
||||
})
|
||||
lien: '',
|
||||
});
|
||||
}
|
||||
|
||||
if (this.projectId != null && this.projectId != 'add'.toLowerCase()) {
|
||||
|
||||
this.projectService.getProjectById(this.projectId).subscribe(
|
||||
value => {
|
||||
this.projectService.getProjectById(this.projectId).subscribe((value) => {
|
||||
this.project = value;
|
||||
this.projectForm.setValue({
|
||||
nom: value.nom,
|
||||
description: value.description,
|
||||
lien: value.lien
|
||||
})
|
||||
lien: value.lien,
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
@@ -73,13 +60,12 @@ export class MyProfileUpdateProjectFormComponent implements OnInit, OnChanges {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (this.projectId != null && this.projectId != 'add'.toLowerCase()) {
|
||||
|
||||
// Update
|
||||
|
||||
this.projectService.updateProject(this.project!.id, this.projectForm.getRawValue()).subscribe(value => {
|
||||
|
||||
this.projectService
|
||||
.updateProject(this.project!.id, this.projectForm.getRawValue())
|
||||
.subscribe((value) => {
|
||||
this.formIsUpdated.emit(value.id);
|
||||
|
||||
this.toastrService.success(
|
||||
@@ -88,44 +74,32 @@ export class MyProfileUpdateProjectFormComponent implements OnInit, OnChanges {
|
||||
{
|
||||
closeButton: true,
|
||||
progressAnimation: 'decreasing',
|
||||
progressBar: true
|
||||
progressBar: true,
|
||||
}
|
||||
);
|
||||
|
||||
})
|
||||
|
||||
});
|
||||
} else {
|
||||
|
||||
// Create
|
||||
|
||||
const projectDto: ProjectDto = {
|
||||
...this.projectForm.getRawValue(),
|
||||
utilisateur: this.authService.user()!.record!.id
|
||||
utilisateur: this.authService.user()!.record!.id,
|
||||
} as ProjectDto;
|
||||
|
||||
|
||||
this.projectService.createProject(projectDto).subscribe(value => {
|
||||
|
||||
this.projectService.createProject(projectDto).subscribe((value) => {
|
||||
this.formIsUpdated.emit(value.id);
|
||||
|
||||
this.toastrService.success(
|
||||
`Le projet ${value.nom} a bien été créer !`,
|
||||
`Nouveau projet`,
|
||||
{
|
||||
this.toastrService.success(`Le projet ${value.nom} a bien été créer !`, `Nouveau projet`, {
|
||||
closeButton: true,
|
||||
progressAnimation: 'decreasing',
|
||||
progressBar: true
|
||||
progressBar: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
this.projectId = changes['projectId'].currentValue;
|
||||
this.ngOnInit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,92 +1,125 @@
|
||||
<header class="w-screen bg-white dark:bg-gray-900 min-h-12">
|
||||
<div class="w-full">
|
||||
<nav
|
||||
class="fixed w-full z-20 top-0 start-1/2 max-w-6xl -translate-x-1/2 bg-white dark:bg-gray-900">
|
||||
<div
|
||||
class="max-w-screen-xl flex flex-nowrap items-center justify-between mx-auto p-4">
|
||||
<a [routerLink]="['/']"
|
||||
class="flex items-center space-x-3 rtl:space-x-reverse">
|
||||
class="fixed w-full z-20 top-0 start-1/2 max-w-6xl -translate-x-1/2 bg-white dark:bg-gray-900"
|
||||
>
|
||||
<div class="max-w-screen-xl flex flex-nowrap items-center justify-between mx-auto p-4">
|
||||
<a [routerLink]="['/']" class="flex items-center space-x-3 rtl:space-x-reverse">
|
||||
<div class="flex flex-row items-center gap-1 mr-3">
|
||||
<span class="text-xl select-none text-black dark:text-white">TrouveTonProfile</span>
|
||||
</div>
|
||||
</a>
|
||||
<div class="flex md:order-2 space-x-1 items-center">
|
||||
|
||||
@if (authService.isAuthenticated() && authService.isEmailVerified()) {
|
||||
|
||||
@if (authService.user()!.record!; as user) {
|
||||
<a [routerLink]="['my-profile']" [state]="{user}" class="w-10 h-10 dark:border-white dark:border rounded-full inline-flex items-center justify-center bg-gray-200 text-gray-400">
|
||||
<a
|
||||
[routerLink]="['my-profile']"
|
||||
[state]="{ user }"
|
||||
class="w-10 h-10 dark:border-white dark:border rounded-full inline-flex items-center justify-center bg-gray-200 text-gray-400"
|
||||
>
|
||||
@if (user.avatar) {
|
||||
<img alt="{{user.username}}" class="object-cover object-center h-full w-full rounded-full"
|
||||
src="{{environment.baseUrl}}/api/files/users/{{user.id}}/{{user.avatar}}" loading="lazy">
|
||||
|
||||
<img
|
||||
alt="{{ user.username }}"
|
||||
class="object-cover object-center h-full w-full rounded-full"
|
||||
src="{{ environment.baseUrl }}/api/files/users/{{ user.id }}/{{ user.avatar }}"
|
||||
loading="lazy"
|
||||
/>
|
||||
} @else {
|
||||
<img alt="{{user.username}}" class="object-cover object-center h-full w-full rounded-full"
|
||||
src="https://api.dicebear.com/9.x/adventurer/svg?seed={{user.username}}" loading="lazy">
|
||||
<img
|
||||
alt="{{ user.username }}"
|
||||
class="object-cover object-center h-full w-full rounded-full"
|
||||
src="https://api.dicebear.com/9.x/adventurer/svg?seed={{ user.username }}"
|
||||
loading="lazy"
|
||||
/>
|
||||
}
|
||||
</a>
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@if (themeService.darkModeSignal() === 'dark') {
|
||||
<button
|
||||
type="button"
|
||||
(click)="toggleDarkMode()"
|
||||
class="text-black dark:text-white font-medium rounded-lg text-sm px-4 py-2 text-center flex items-center justify-center gap-1">
|
||||
class="text-black dark:text-white font-medium rounded-lg text-sm px-4 py-2 text-center flex items-center justify-center gap-1"
|
||||
>
|
||||
<span class="inline-block h-4 w-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z"/>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-6"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
</span>
|
||||
</button>
|
||||
} @else {
|
||||
<button
|
||||
type="button"
|
||||
(click)="toggleDarkMode()"
|
||||
class="text-black dark:text-white font-medium rounded-lg text-sm px-4 py-2 text-center flex items-center justify-center gap-1">
|
||||
class="text-black dark:text-white font-medium rounded-lg text-sm px-4 py-2 text-center flex items-center justify-center gap-1"
|
||||
>
|
||||
<span class="inline-block h-4 w-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-full h-full fill-current"
|
||||
viewBox="0 -960 960 960">
|
||||
viewBox="0 -960 960 960"
|
||||
>
|
||||
<path
|
||||
d="M480-120q-150 0-255-105T120-480q0-150 105-255t255-105q14 0 27.5 1t26.5 3q-41 29-65.5 75.5T444-660q0 90 63 153t153 63q55 0 101-24.5t75-65.5q2 13 3 26.5t1 27.5q0 150-105 255T480-120Zm0-80q88 0 158-48.5T740-375q-20 5-40 8t-40 3q-123 0-209.5-86.5T364-660q0-20 3-40t8-40q-78 32-126.5 102T200-480q0 116 82 198t198 82Zm-10-270Z"/>
|
||||
d="M480-120q-150 0-255-105T120-480q0-150 105-255t255-105q14 0 27.5 1t26.5 3q-41 29-65.5 75.5T444-660q0 90 63 153t153 63q55 0 101-24.5t75-65.5q2 13 3 26.5t1 27.5q0 150-105 255T480-120Zm0-80q88 0 158-48.5T740-375q-20 5-40 8t-40 3q-123 0-209.5-86.5T364-660q0-20 3-40t8-40q-78 32-126.5 102T200-480q0 116 82 198t198 82Zm-10-270Z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
<span class="text-black dark:text-white"> | </span>
|
||||
|
||||
|
||||
@if (authService.user()?.isValid) {
|
||||
<a [routerLink]="['/auth']" (click)="authService.logout(); authService.updateUser();"
|
||||
class="text-black dark:text-white font-medium rounded-lg text-sm px-4 py-2 text-center flex items-center justify-center space-x-4">
|
||||
<a
|
||||
[routerLink]="['/auth']"
|
||||
(click)="authService.logout(); authService.updateUser()"
|
||||
class="text-black dark:text-white font-medium rounded-lg text-sm px-4 py-2 text-center flex items-center justify-center space-x-4"
|
||||
>
|
||||
<span class="h-4 w-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M15.75 9V5.25A2.25 2.25 0 0 0 13.5 3h-6a2.25 2.25 0 0 0-2.25 2.25v13.5A2.25 2.25 0 0 0 7.5 21h6a2.25 2.25 0 0 0 2.25-2.25V15M12 9l-3 3m0 0 3 3m-3-3h12.75"/>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-6"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15.75 9V5.25A2.25 2.25 0 0 0 13.5 3h-6a2.25 2.25 0 0 0-2.25 2.25v13.5A2.25 2.25 0 0 0 7.5 21h6a2.25 2.25 0 0 0 2.25-2.25V15M12 9l-3 3m0 0 3 3m-3-3h12.75"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
</span>
|
||||
<span class="hidden sm:block text-black text-center dark:text-white">Se deconnecter</span>
|
||||
<span class="hidden sm:block text-black text-center dark:text-white"
|
||||
>Se deconnecter</span
|
||||
>
|
||||
</a>
|
||||
} @else {
|
||||
<a [routerLink]="['/auth']"
|
||||
class="text-black dark:text-white font-medium rounded-lg text-sm px-4 py-2 text-center flex items-center justify-center gap-1">
|
||||
<a
|
||||
[routerLink]="['/auth']"
|
||||
class="text-black dark:text-white font-medium rounded-lg text-sm px-4 py-2 text-center flex items-center justify-center gap-1"
|
||||
>
|
||||
<span class="inline-block h-4 w-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-full h-full fill-current"
|
||||
viewBox="0 -960 960 960">
|
||||
viewBox="0 -960 960 960"
|
||||
>
|
||||
<path
|
||||
d="M234-276q51-39 114-61.5T480-360q69 0 132 22.5T726-276q35-41 54.5-93T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 59 19.5 111t54.5 93Zm246-164q-59 0-99.5-40.5T340-580q0-59 40.5-99.5T480-720q59 0 99.5 40.5T620-580q0 59-40.5 99.5T480-440Zm0 360q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q53 0 100-15.5t86-44.5q-39-29-86-44.5T480-280q-53 0-100 15.5T294-220q39 29 86 44.5T480-160Zm0-360q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm0-60Zm0 360Z"/>
|
||||
d="M234-276q51-39 114-61.5T480-360q69 0 132 22.5T726-276q35-41 54.5-93T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 59 19.5 111t54.5 93Zm246-164q-59 0-99.5-40.5T340-580q0-59 40.5-99.5T480-720q59 0 99.5 40.5T620-580q0 59-40.5 99.5T480-440Zm0 360q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q53 0 100-15.5t86-44.5q-39-29-86-44.5T480-280q-53 0-100 15.5T294-220q39 29 86 44.5T480-160Zm0-360q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm0-60Zm0 360Z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="hidden sm:block text-black dark:text-white">Se connecter</span>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NavBarComponent } from './nav-bar.component';
|
||||
import {ThemeService} from "@app/core/services/theme/theme.service";
|
||||
import {AuthService} from "@app/core/services/authentication/auth.service";
|
||||
import { ThemeService } from '@app/core/services/theme/theme.service';
|
||||
import { AuthService } from '@app/core/services/authentication/auth.service';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import {signal} from "@angular/core";
|
||||
import {Auth} from "@app/shared/models/auth";
|
||||
import {User} from "@app/shared/models/user";
|
||||
import { signal } from '@angular/core';
|
||||
import { Auth } from '@app/shared/models/auth';
|
||||
import { User } from '@app/shared/models/user';
|
||||
|
||||
describe('NavBarComponent', () => {
|
||||
let component: NavBarComponent;
|
||||
@@ -16,27 +16,27 @@ describe('NavBarComponent', () => {
|
||||
|
||||
const user: User = {
|
||||
id: 'adbc123',
|
||||
username: "john_doe",
|
||||
username: 'john_doe',
|
||||
verified: true,
|
||||
emailVisibility: false,
|
||||
email: "jd@example.com",
|
||||
email: 'jd@example.com',
|
||||
created: new Date().toString(),
|
||||
updated: new Date().toString(),
|
||||
name: "john doe",
|
||||
avatar: ""
|
||||
name: 'john doe',
|
||||
avatar: '',
|
||||
};
|
||||
const mockUser : Auth = {isValid:false, record: user, token: "mockToken123"} as Auth;
|
||||
const mockUser: Auth = { isValid: false, record: user, token: 'mockToken123' } as Auth;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockAuthService={
|
||||
mockAuthService = {
|
||||
updateUser: jest.fn(),
|
||||
user: signal<Auth | undefined>(mockUser),
|
||||
isAuthenticated: jest.fn().mockReturnValue(true),
|
||||
isEmailVerified: jest.fn().mockReturnValue(true)
|
||||
}
|
||||
isEmailVerified: jest.fn().mockReturnValue(true),
|
||||
};
|
||||
mockThemeService = {
|
||||
darkModeSignal: signal<string>('null'),
|
||||
updateDarkMode: jest.fn()
|
||||
updateDarkMode: jest.fn(),
|
||||
};
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
@@ -44,10 +44,9 @@ describe('NavBarComponent', () => {
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
{ provide: ThemeService, useValue: mockThemeService },
|
||||
{ provide: AuthService, useValue: mockAuthService }
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
{ provide: AuthService, useValue: mockAuthService },
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(NavBarComponent);
|
||||
component = fixture.componentInstance;
|
||||
@@ -58,7 +57,6 @@ describe('NavBarComponent', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
it('should call authService.updateUser on ngOnInit', () => {
|
||||
expect(mockAuthService.updateUser).toHaveBeenCalled();
|
||||
});
|
||||
@@ -67,7 +65,4 @@ describe('NavBarComponent', () => {
|
||||
component.toggleDarkMode();
|
||||
expect(mockThemeService.updateDarkMode).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
import {Component, inject, OnInit} from '@angular/core';
|
||||
import {RouterLink} from "@angular/router";
|
||||
import {ThemeService} from "@app/core/services/theme/theme.service";
|
||||
import {AuthService} from "@app/core/services/authentication/auth.service";
|
||||
import {UntilDestroy} from "@ngneat/until-destroy";
|
||||
import {environment} from "@env/environment";
|
||||
import { Component, inject, OnInit } from '@angular/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { ThemeService } from '@app/core/services/theme/theme.service';
|
||||
import { AuthService } from '@app/core/services/authentication/auth.service';
|
||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||
import { environment } from '@env/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-nav-bar',
|
||||
standalone: true,
|
||||
imports: [
|
||||
RouterLink
|
||||
],
|
||||
imports: [RouterLink],
|
||||
templateUrl: './nav-bar.component.html',
|
||||
styleUrl: './nav-bar.component.scss'
|
||||
styleUrl: './nav-bar.component.scss',
|
||||
})
|
||||
@UntilDestroy()
|
||||
export class NavBarComponent implements OnInit {
|
||||
protected themeService: ThemeService = inject(ThemeService);
|
||||
protected authService = inject(AuthService);
|
||||
|
||||
|
||||
toggleDarkMode() {
|
||||
this.themeService.updateDarkMode();
|
||||
}
|
||||
|
||||
@@ -1,21 +1,39 @@
|
||||
@if (project) {
|
||||
<div class="bg-white rounded-2xl border p-6">
|
||||
|
||||
@if (project.fichier) {
|
||||
<img alt="{{project.nom}}" class="object-cover object-center h-full w-full "
|
||||
src="{{environment.baseUrl}}/api/files/projets/{{project.id}}/{{project.fichier}}" loading="lazy">
|
||||
<img
|
||||
alt="{{ project.nom }}"
|
||||
class="object-cover object-center h-full w-full"
|
||||
src="{{ environment.baseUrl }}/api/files/projets/{{ project.id }}/{{ project.fichier }}"
|
||||
loading="lazy"
|
||||
/>
|
||||
} @else {
|
||||
<img alt="nouveau-projet" class="object-cover object-center h-full w-full "
|
||||
src="https://api.dicebear.com/9.x/shapes/svg?seed={{project.nom}}" loading="lazy">
|
||||
<img
|
||||
alt="nouveau-projet"
|
||||
class="object-cover object-center h-full w-full"
|
||||
src="https://api.dicebear.com/9.x/shapes/svg?seed={{ project.nom }}"
|
||||
loading="lazy"
|
||||
/>
|
||||
}
|
||||
|
||||
<div class="mt-6">
|
||||
<h3 class="text-lg font-bold text-gray-800 mb-3">{{ project.nom }}</h3>
|
||||
<p class="text-gray-800 text-sm">{{ project.description }}</p>
|
||||
<div class="mt-6">
|
||||
<a class="text-indigo-500 inline-flex items-center md:mb-2 lg:mb-0" href="{{project.lien}}" target="_blank">Explore
|
||||
<svg class="w-4 h-4 ml-2" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<a
|
||||
class="text-indigo-500 inline-flex items-center md:mb-2 lg:mb-0"
|
||||
href="{{ project.lien }}"
|
||||
target="_blank"
|
||||
>Explore
|
||||
<svg
|
||||
class="w-4 h-4 ml-2"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M5 12h14"></path>
|
||||
<path d="M12 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
|
||||
@@ -8,9 +8,8 @@ describe('ProjectItemComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ProjectItemComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
imports: [ProjectItemComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ProjectItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {Project} from "@app/shared/models/project";
|
||||
import {JsonPipe} from "@angular/common";
|
||||
import {environment} from "@env/environment";
|
||||
import {RouterLink} from "@angular/router";
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Project } from '@app/shared/models/project';
|
||||
import { JsonPipe } from '@angular/common';
|
||||
import { environment } from '@env/environment';
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-project-item',
|
||||
standalone: true,
|
||||
imports: [
|
||||
JsonPipe,
|
||||
RouterLink
|
||||
],
|
||||
imports: [JsonPipe, RouterLink],
|
||||
templateUrl: './project-item.component.html',
|
||||
styleUrl: './project-item.component.scss'
|
||||
styleUrl: './project-item.component.scss',
|
||||
})
|
||||
export class ProjectItemComponent {
|
||||
protected readonly environment = environment;
|
||||
|
||||
@Input({required: true}) project: Project | undefined = undefined;
|
||||
|
||||
@Input({ required: true }) project: Project | undefined = undefined;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user