diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..e8d514c8d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "git.mergeEditor": true, + "merge-conflict.autoNavigateNextConflict.enabled": true +} \ No newline at end of file diff --git a/README.md b/README.md index e03630b34..8b1378917 100644 --- a/README.md +++ b/README.md @@ -1,178 +1 @@ -# [Light Bootstrap Dashboard Angular](https://demos.creative-tim.com/light-bootstrap-dashboard-angular2/dashboard) -[![version][version-badge]][CHANGELOG] ![license][license-badge] -![alt text](src/assets/img/opt_lbd_angular_thumbnail.jpg) - -**[Light Bootstrap Dashboard Angular](https://demos.creative-tim.com/light-bootstrap-dashboard-angular2/dashboard)** is an admin dashboard template designed to be beautiful and simple. It is built on top of Bootstrap 3, using [Light Bootstrap Dashboard](https://www.creative-tim.com/product/light-bootstrap-dashboard) and it is fully responsive. It comes with a big collections of elements that will offer you multiple possibilities to create the app that best fits your needs. It can be used to create admin panels, project management systems, web applications backend, CMS or CRM. - -The product represents a big suite of front-end developer tools that can help you jump start your project. We have created it thinking about things you actually need in a dashboard. Light Bootstrap Dashboard Angular 2 contains multiple handpicked and optimized plugins. Everything is designed to fit with one another. As you will be able to see, the dashboard you can access on Creative Tim is a customization of this product. - -It comes with 6 filter colors for the sidebar (“black”, “azure”,”green”,”orange”,”red”,”purple”) and an option to have a background image. - -Special thanks go to: Robert McIntosh for the notification system Chartist for the wonderful charts We are very excited to share this dashboard with you and we look forward to hearing your feedback! - -## Links: - -+ [Live Preview](https://demos.creative-tim.com/light-bootstrap-dashboard-angular2/dashboard) -+ [Light Bootstrap Dashboard PRO Angular](https://www.creative-tim.com/product/light-bootstrap-dashboard-pro-angular2/?ref=lbd-angular-github) ($49) - -## Quick Start: - -Quick start options: - -+ [Download from Github](https://github.com/creativetimofficial/light-bootstrap-dashboard-angular2/archive/master.zip). -+ [Download from Creative Tim](https://www.creative-tim.com/product/light-bootstrap-dashboard-angular2). -+ Clone the repo: `git clone https://github.com/creativetimofficial/light-bootstrap-dashboard-angular2.git`. - -## Deploy - -:rocket: You can deploy your own version of the template to Genezio with one click: - -[![Deploy to Genezio](https://raw.githubusercontent.com/Genez-io/graphics/main/svg/deploy-button.svg)](https://app.genez.io/start/deploy?repository=https://github.com/creativetimofficial/light-bootstrap-dashboard-angular2&utm_source=github&utm_medium=referral&utm_campaign=github-creativetim&utm_term=deploy-project&utm_content=button-head) - -## Terminal Commands - -1. Install NodeJs from [NodeJs Official Page](https://nodejs.org/en). -2. Open Terminal -3. Go to your file project -4. Run in terminal: ```npm install -g @angular/cli``` -5. Then: ```npm install``` -6. And: ```ng serve``` -7. Navigate to `http://localhost:4200/` - -### What's included - -Within the download you'll find the following directories and files: -``` -light-bootstrap-dashboard-angular -├── CHANGELOG.md -├── LICENSE.md -├── README.md -├── angular.json -├── documentation -│   ├── css -│   └── tutorial-lbd-angular2.html -├── e2e -├── karma.conf.js -├── package-lock.json -├── package.json -├── protractor.conf.js -├── src -│   ├── app -│   │   ├── app.component.css -│   │   ├── app.component.html -│   │   ├── app.component.spec.ts -│   │   ├── app.component.ts -│   │   ├── app.module.ts -│   │   ├── app.routing.ts -│   │   ├── home -│   │   │   ├── home.component.css -│   │   │   ├── home.component.html -│   │   │   ├── home.component.spec.ts -│   │   │   └── home.component.ts -│   │   ├── icons -│   │   │   ├── icons.component.css -│   │   │   ├── icons.component.html -│   │   │   ├── icons.component.spec.ts -│   │   │   └── icons.component.ts -│   │   ├── layouts -│   │   │   └── admin-layout -│   │   │   ├── admin-layout.component.html -│   │   │   ├── admin-layout.component.scss -│   │   │   ├── admin-layout.component.spec.ts -│   │   │   ├── admin-layout.component.ts -│   │   │   ├── admin-layout.module.ts -│   │   │   └── admin-layout.routing.ts -│   │   ├── lbd -│   │   │   ├── lbd-chart -│   │   │   │   ├── lbd-chart.component.html -│   │   │   │   └── lbd-chart.component.ts -│   │   │   └── lbd.module.ts -│   │   ├── maps -│   │   │   ├── maps.component.css -│   │   │   ├── maps.component.html -│   │   │   ├── maps.component.spec.ts -│   │   │   └── maps.component.ts -│   │   ├── notifications -│   │   │   ├── notifications.component.css -│   │   │   ├── notifications.component.html -│   │   │   ├── notifications.component.spec.ts -│   │   │   └── notifications.component.ts -│   │   ├── shared -│   │   │   ├── footer -│   │   │   │   ├── footer.component.html -│   │   │   │   ├── footer.component.ts -│   │   │   │   └── footer.module.ts -│   │   │   └── navbar -│   │   │   ├── navbar.component.html -│   │   │   ├── navbar.component.ts -│   │   │   └── navbar.module.ts -│   │   ├── sidebar -│   │   │   ├── sidebar.component.html -│   │   │   ├── sidebar.component.ts -│   │   │   └── sidebar.module.ts -│   │   ├── tables -│   │   │   ├── tables.component.css -│   │   │   ├── tables.component.html -│   │   │   ├── tables.component.spec.ts -│   │   │   └── tables.component.ts -│   │   ├── typography -│   │   │   ├── typography.component.css -│   │   │   ├── typography.component.html -│   │   │   ├── typography.component.spec.ts -│   │   │   └── typography.component.ts -│   │   ├── upgrade -│   │   │   ├── upgrade.component.css -│   │   │   ├── upgrade.component.html -│   │   │   ├── upgrade.component.spec.ts -│   │   │   └── upgrade.component.ts -│   │   └── user -│   │   ├── user.component.css -│   │   ├── user.component.html -│   │   ├── user.component.spec.ts -│   │   └── user.component.ts -│   ├── assets -│   │   ├── css -│   │   ├── fonts -│   │   ├── img -│   │   └── sass -│   │   ├── lbd -│   │   └── light-bootstrap-dashboard.scss -│   ├── environments -│   ├── favicon.ico -│   ├── index.html -│   ├── main.ts -│   ├── polyfills.ts -│   ├── styles.css -│   ├── test.ts -│   └── tsconfig.json -├── tslint.json -└── typings.json - -``` -## Useful Links - -More products from Creative Tim: - -Tutorials: - -Freebies: - -Affiliate Program (earn money): - -Social Media: - -Twitter: - -Facebook: - -Dribbble: - -Google+: - -Instagram: - -[CHANGELOG]: ./CHANGELOG.md - -[version-badge]: https://img.shields.io/badge/version-1.9.0-blue.svg -[license-badge]: https://img.shields.io/badge/license-MIT-blue.svg diff --git a/angular.json b/angular.json index b232b7f9f..3ca145eff 100644 --- a/angular.json +++ b/angular.json @@ -19,21 +19,37 @@ "assets": [ "src/assets", "src/favicon.ico" + ], "styles": [ "node_modules/perfect-scrollbar/css/perfect-scrollbar.css", "node_modules/animate.css/animate.min.css", "node_modules/bootstrap/dist/css/bootstrap.min.css", "src/assets/sass/light-bootstrap-dashboard.scss", - "src/assets/css/demo.css" + "src/assets/css/demo.css", + "src/styles.css", + "src/assets/css/bootstrap.min.css", + "src/assets/css/tooplate-infinite-loop.css", + "src/assets/fontawesome-5.5/css/all.min.css", + "src/assets/magnific-popup/magnific-popup.css", + "src/assets/slick/slick.css", + "src/assets/slick/slick-theme.css", + "node_modules/intl-tel-input/build/css/intlTelInput.css" ], "scripts": [ "node_modules/jquery/dist/jquery.js", "node_modules/bootstrap/dist/js/bootstrap.js", "node_modules/bootstrap-notify/bootstrap-notify.js", - "node_modules/chartist/dist/chartist.js" - ] - }, + "node_modules/chartist/dist/chartist.js", + "src/assets/js/jquery-1.9.1.min.js", + "src/assets/slick/slick.min.js", + "src/assets/magnific-popup/jquery.magnific-popup.min.js", + "src/assets/js/easing.min.js", + "src/assets/js/jquery.singlePageNav.min.js", + "src/assets/js/bootstrap.min.js", + "node_modules/html2pdf.js/dist/html2pdf.bundle.min.js", + "node_modules/intl-tel-input/build/js/intlTelInput.min.js" + ] }, "configurations": { "production": { "optimization": true, @@ -62,7 +78,7 @@ "minify": false, "inlineCritical": true }, - "fonts": true + "fonts": false }, "outputHashing": "all" } diff --git a/package.json b/package.json index c51c02de7..14fffd0f1 100644 --- a/package.json +++ b/package.json @@ -24,41 +24,55 @@ "@angular/material": "^14.2.0", "@angular/platform-browser": "^14.2.0", "@angular/platform-browser-dynamic": "^14.2.0", - "@angular/router": "^14.2.0", + "@angular/router": "^14.3.0", "@ngui/map": "0.30.3", + "@ngx-translate/core": "^14.0.0", + "@ngx-translate/http-loader": "^7.0.0", + "@popperjs/core": "^2.11.8", "@types/googlemaps": "3.43.3", "animate.css": "4.1.1", "arrive": "2.4.1", - "bootstrap": "3.3.7", + "bootstrap": "^5.3.6", "bootstrap-notify": "3.1.3", "chartist": "0.11.4", + "emailjs-com": "^3.2.0", "googleapis": "66.0.0", - "jquery": "3.5.1", + "html2pdf.js": "^0.10.3", + "intl-tel-input": "^25.3.1", + + + + "jquery": "^3.7.1", + "ngx-slickjs": "^1.5.2", + "perfect-scrollbar": "1.5.0", "rxjs": "~7.5.0", + "slick-carousel": "^1.8.1", + "sweetalert2": "^11.22.0", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, "devDependencies": { - "@angular-devkit/build-angular": "^14.2.3", + "@angular-devkit/build-angular": "^14.2.0", "@angular/cli": "~14.2.3", "@angular/compiler-cli": "^14.2.0", + "@types/chartist": "0.11.0", + "@types/intl-tel-input": "^18.1.4", "@types/jasmine": "~5.1.4", + "@types/jasminewd2": "~2.0.13", + "@types/jquery": "3.5.30", + "@types/node": "20.14.11", + "codelyzer": "^0.0.28", + "cross-env": "^7.0.3", "jasmine-core": "~4.3.0", + "jasmine-spec-reporter": "~7.0.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.0.0", - "typescript": "~4.7.2", - "@types/jasminewd2": "~2.0.13", - "@types/chartist": "0.11.0", - "@types/jquery": "3.5.30", - "@types/node": "20.14.11", - "codelyzer": "6.0.2", - "jasmine-spec-reporter": "~7.0.0", - "protractor": "7.0.0", + "protractor": "^7.0.0", "ts-node": "~10.7.0", - "cross-env": "^7.0.3" + "typescript": "~4.7.2" } } diff --git a/src/app/Services/BlogService.ts b/src/app/Services/BlogService.ts new file mode 100644 index 000000000..8b5974647 --- /dev/null +++ b/src/app/Services/BlogService.ts @@ -0,0 +1,73 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { environment } from 'environments/environment'; +import { Blog } from 'app/blogslist/blogslist.component'; + +export interface BlogCreateRequest { + titre: string; + contenu: string; + userId: number; + tags: string[]; + image: File | null; +} + +export interface BlogUpdateRequest { + titre: string; + contenu: string; + newImage?: File; + tags: string[]; +} +@Injectable({ + providedIn: 'root' +}) +export class BlogService { + private apiUrl = environment.apiUrl + '/blog'; + + constructor(private http: HttpClient) { } + + getAllBlogs(): Observable { + return this.http.get(this.apiUrl); + } + + createBlog(request: BlogCreateRequest): Observable { + const formData = new FormData(); + + formData.append('Titre', request.titre); + formData.append('Contenu', request.contenu); + formData.append('UserId', request.userId.toString()); + request.tags.forEach(tag => formData.append('Tags', tag)); + + if (request.image) { + formData.append('Image', request.image, request.image.name); + } + + return this.http.post(this.apiUrl, formData); + } +updateBlog(id: number, request: BlogUpdateRequest): Observable { + const formData = new FormData(); + formData.append('Titre', request.titre); + formData.append('Contenu', request.contenu); + + if (request.newImage) { + formData.append('NewImage', request.newImage, request.newImage.name); + } + + request.tags.forEach(tag => { + formData.append('Tags', tag); + }); + + return this.http.put(`${this.apiUrl}/${id}`, formData); +} + + + +getBlogById(id: number) { + return this.http.get(`${this.apiUrl}/${id}`); +} + +deleteBlog(id: number) { + return this.http.delete(`${this.apiUrl}/${id}`); +} + +} diff --git a/src/app/Services/ProduitAvecDevisService.ts b/src/app/Services/ProduitAvecDevisService.ts new file mode 100644 index 000000000..107cf895c --- /dev/null +++ b/src/app/Services/ProduitAvecDevisService.ts @@ -0,0 +1,103 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'environments/environment'; +import { Observable } from 'rxjs'; + +export interface ProduitAvecDevis { + id: number; + titre: string; + description: string; + categorie: string; + imagePath: string; + caracteristiques: { + id: number; + texte: string; + produitAvecDevisId: number; + }[]; + devis?: { + id: number; + nom: string; + prenom: string; + email: string; + entreprise: string; + message: string; + quantite: number; + userId: number; + produitAvecDevisId: number; + }[]; +} + +export interface ProduitAvecDevisCreateRequest { + titre: string; + description: string; + categorie: string; + image: File | null; + caracteristiques: string[]; +} + +export interface ProduitAvecDevisUpdateRequest { + titre: string; + description: string; + categorie: string; + newImage?: File | null; + caracteristiques: string[]; +} + +@Injectable({ + providedIn: 'root' +}) +export class ProduitAvecDevisService { + private apiUrl = environment.apiUrl + '/ProduitsAvecDevis'; + + constructor(private http: HttpClient) {} + + getAllProduits(): Observable { + return this.http.get(this.apiUrl); + } + + getById(id: number): Observable { + return this.http.get(`${this.apiUrl}/${id}`); + } + + create(request: ProduitAvecDevisCreateRequest): Observable { + const formData = new FormData(); + + formData.append('Titre', request.titre); + formData.append('Description', request.description); + formData.append('Categorie', request.categorie); + + request.caracteristiques.forEach(c => { + formData.append('Caracteristiques', c); + }); + + if (request.image) { + formData.append('Image', request.image, request.image.name); + } + + return this.http.post(this.apiUrl, formData); + } + + update(id: number, request: ProduitAvecDevisUpdateRequest): Observable { + const formData = new FormData(); + + formData.append('Titre', request.titre); + formData.append('Description', request.description); + formData.append('Categorie', request.categorie); + + request.caracteristiques.forEach(c => { + formData.append('Caracteristiques', c); + }); + + if (request.newImage) { + formData.append('NewImage', request.newImage, request.newImage.name); + } + + return this.http.put(`${this.apiUrl}/${id}`, formData); + } + + delete(id: number): Observable { + return this.http.delete(`${this.apiUrl}/${id}`); + } + + +} diff --git a/src/app/Services/ProduitSansDevisService.ts b/src/app/Services/ProduitSansDevisService.ts new file mode 100644 index 000000000..56242817d --- /dev/null +++ b/src/app/Services/ProduitSansDevisService.ts @@ -0,0 +1,98 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'environments/environment'; +import { Observable } from 'rxjs'; + +export interface ProduitSansDevis { + id: number; + titre: string; + description: string; + categorie: string; + prix: string; + imagePath: string; + caracteristiques: { + id: number; + texte: string; + produitSansDevisId: number; + }[]; + userId: number; +} + +export interface ProduitSansDevisCreateRequest { + titre: string; + description: string; + categorie: string; + prix: string; + image: File | null; + caracteristiques: string[]; + userId: number; +} + +export interface ProduitSansDevisUpdateRequest { + titre: string; + description: string; + categorie: string; + prix: string; + newImage?: File | null; + caracteristiques: string[]; +} + +@Injectable({ + providedIn: 'root' +}) +export class ProduitSansDevisService { + private apiUrl = environment.apiUrl + '/ProduitsSansDevis'; + + constructor(private http: HttpClient) {} + + getAllProduits(): Observable { + return this.http.get(this.apiUrl); + } + + getById(id: number): Observable { + return this.http.get(`${this.apiUrl}/${id}`); + } + + create(request: ProduitSansDevisCreateRequest): Observable { + const formData = new FormData(); + + formData.append('Titre', request.titre); + formData.append('Description', request.description); + formData.append('Categorie', request.categorie); + formData.append('Prix', request.prix); + formData.append('UserId', request.userId.toString()); + + request.caracteristiques.forEach(c => { + formData.append('Caracteristiques', c); + }); + + if (request.image) { + formData.append('Image', request.image, request.image.name); + } + + return this.http.post(this.apiUrl, formData); + } + + update(id: number, request: ProduitSansDevisUpdateRequest): Observable { + const formData = new FormData(); + + formData.append('Titre', request.titre); + formData.append('Description', request.description); + formData.append('Categorie', request.categorie); + formData.append('Prix', request.prix); + + request.caracteristiques.forEach(c => { + formData.append('Caracteristiques', c); + }); + + if (request.newImage) { + formData.append('NewImage', request.newImage, request.newImage.name); + } + + return this.http.put(`${this.apiUrl}/${id}`, formData); + } + + delete(id: number): Observable { + return this.http.delete(`${this.apiUrl}/${id}`); + } +} diff --git a/src/app/Services/UserService.ts b/src/app/Services/UserService.ts new file mode 100644 index 000000000..9b06a5211 --- /dev/null +++ b/src/app/Services/UserService.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { environment } from 'environments/environment'; + +export interface User { + id?: number; + nom: string; + email: string; + role: string; +} + +@Injectable({ + providedIn: 'root' +}) +export class UserService { + private apiUrl = environment.apiUrl + '/user'; + + constructor(private http: HttpClient) {} + + getUsers(): Observable { + return this.http.get(this.apiUrl); + } +} diff --git a/src/app/Services/auth.service.ts b/src/app/Services/auth.service.ts new file mode 100644 index 000000000..5ed7bdd7a --- /dev/null +++ b/src/app/Services/auth.service.ts @@ -0,0 +1,32 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { environment } from 'environments/environment'; +import { Observable } from 'rxjs'; + +export interface SignUpRequest { + nom: string; + email: string; + role:number; +} + +export interface SignInRequest { + email: string; + password: string; +} + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + private apiUrl = `${environment.apiUrl}/auth`; + + constructor(private http: HttpClient) {} + + signUp(data: SignUpRequest): Observable { + return this.http.post(`${this.apiUrl}/signup`, data); + } + + signIn(data: SignInRequest): Observable { + return this.http.post(`${this.apiUrl}/signin`, data); + } +} diff --git a/src/app/Services/country.service.ts b/src/app/Services/country.service.ts new file mode 100644 index 000000000..ec9c793bf --- /dev/null +++ b/src/app/Services/country.service.ts @@ -0,0 +1,17 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class CountryService { + + private apiUrl = 'https://restcountries.com/v3.1/all'; + + constructor(private http: HttpClient) {} + + getCountries(): Observable { + return this.http.get(this.apiUrl); + } +} diff --git a/src/app/Services/franchise.service.ts b/src/app/Services/franchise.service.ts new file mode 100644 index 000000000..f5e03dcb1 --- /dev/null +++ b/src/app/Services/franchise.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import { Franchise } from 'app/liste-franchises/liste-franchises.component'; +import { HttpClient, HttpParams } from '@angular/common/http'; + +import { Observable } from 'rxjs'; +import { environment } from 'environments/environment'; + +@Injectable({ + providedIn: 'root' +}) +export class FranchiseService { + private apiUrl = environment.apiUrl + '/franchise'; + + constructor(private http: HttpClient) { } + + getFranchises(): Observable { + return this.http.get(this.apiUrl); + } + + getFranchiseById(id: number): Observable { + return this.http.get(`${this.apiUrl}/${id}`); +} +envoyerDemandeFranchise(payload: any) { + return this.http.post(this.apiUrl, payload); + } +} diff --git a/src/app/Services/language.service.ts b/src/app/Services/language.service.ts new file mode 100644 index 000000000..a59ddf510 --- /dev/null +++ b/src/app/Services/language.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; + +@Injectable({ + providedIn: 'root' +}) +export class LanguageService { +constructor(private translate: TranslateService) { + const savedLanguage = localStorage.getItem('language') || 'en'; + this.setLanguage(savedLanguage); + } + + setLanguage(lang: string) { + console.log('Changement de langue vers :', lang); + this.translate.use(lang).subscribe({ + next: () => console.log(`Langue ${lang} activée`), + error: (err) => console.error(`Erreur de chargement pour ${lang}`, err) + }); + localStorage.setItem('language', lang); +} + + + getCurrentLanguage(): string { + return this.translate.currentLang || 'en'; + } +} diff --git a/src/app/Services/scroll.service.ts b/src/app/Services/scroll.service.ts new file mode 100644 index 000000000..8a4d150c5 --- /dev/null +++ b/src/app/Services/scroll.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class ScrollService { + private anchor: string | null = null; + + setAnchor(anchor: string) { + this.anchor = anchor; + } + + getAnchor(): string | null { + const a = this.anchor; + this.anchor = null; + return a; + } +} diff --git a/src/app/about-us/about-us.component.html b/src/app/about-us/about-us.component.html new file mode 100644 index 000000000..e608e052b --- /dev/null +++ b/src/app/about-us/about-us.component.html @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + +
+
+

{{ 'ABOUT.TITLE' | translate }}

+
+
+ +
+
+
+
+ + +
+
+

{{ 'ABOUT.FIRST.PARAGRAPH' | translate }}

+ {{ 'ABOUT.FIRST.CTA' | translate }} +
+
+
+
+ +
+
+
+
+ +
+
+
+

{{ 'ABOUT.SECOND.TITLE' | translate }}

+

{{ 'ABOUT.SECOND.PARAGRAPH' | translate }}

+ {{ 'ABOUT.SECOND.CTA' | translate }} +
+
+
+ +
+
+
+

{{ 'ABOUT.THIRD.TITLE' | translate }}

+

{{ 'ABOUT.THIRD.PARAGRAPH' | translate }}

+ {{ 'ABOUT.THIRD.CTA' | translate }} +
+
+
+ +
+
+
+
+ +
+

{{ 'ABOUT.FOURTH.TITLE' | translate }}

+ +
+ +
+
+

{{ 'ABOUT.SIXTH.TITLE' | translate }}

+

{{ 'ABOUT.SIXTH.PARAGRAPH1' | translate }}

+ Collaboration Tunav IT Group +

{{ 'ABOUT.SIXTH.PARAGRAPH2' | translate }}

+ {{ 'ABOUT.SIXTH.CTA' | translate }} +
+
+


+ + + diff --git a/src/app/about-us/about-us.component.scss b/src/app/about-us/about-us.component.scss new file mode 100644 index 000000000..2b7a15b0f --- /dev/null +++ b/src/app/about-us/about-us.component.scss @@ -0,0 +1,503 @@ +/* about-page.scss - Styles spécifiques à la page About */ +.about-page { + margin: 0; + padding: 0; + font-family: 'Roboto', sans-serif; + background-color: #F8F9FA; + color: #222222; + font-weight: 400; + + strong { + font-weight: 700; + } + + img { + max-width: 100%; + } + + p { + font-size: 1.125rem; + color: #222222; + margin: 0 0 15px; + line-height: 1.6; + } + + h1, h2, h3, h4, h5, h6 { + margin: 0; + } + + h1 { + color: #000000; + font-size: 36px; + @media (min-width: 768px) { + font-size: 80px; + } + } + + h2 { + color: #222222; + font-size: 30px; + @media (min-width: 768px) { + font-size: 60px; + } + } + + h3 { + color: #444444; + font-size: 24px; + @media (min-width: 768px) { + font-size: 50px; + } + } + + h4 { + color: #555555; + font-size: 22px; + @media (min-width: 768px) { + font-size: 40px; + } + } + + .about-container { + max-width: 1100px; + margin: 0 auto; + + img { + padding: 0.25rem; + border: 1px solid #bdbdbd; + border-radius: 0.25rem; + } + } + + .about-cta { + padding: 10px 30px; + text-align: center; + text-decoration: none; + background-color: #369; + border: 1px solid #369; + border-radius: 25px; + color: #FFFFFF; + text-transform: uppercase; + display: inline-block; + box-shadow: rgba(100, 100, 111, 0.8) 0px 7px 19px 0px; + transition: all 0.8s ease; + + &:hover { + background-color: #369; + border: 1px solid #3b1215; + color: #000000; + } + } + + /* Sections spécifiques */ + .about-banner { + background: linear-gradient(90deg, rgba(51, 85, 119, 0.8), rgba(0, 0, 0, 0.5)), url("/assets/img/TunavEquipe.png") no-repeat; + background-position: center; + background-size: cover; + padding: 150px 15px; + + h1 { + color: rgba($color: #FFFFFF, $alpha: 0.85); + text-transform: uppercase; + font-weight: 700; + } + + p { + color: #FFFFFF; + font-size: 1.375rem; + letter-spacing: 1.5px; + font-weight: 100; + text-shadow: 2px 2px 7px #222222; + } + } + + .about-section { + padding: 90px 15px; + } + + .about-first { + background-color: #FFFFFF; + background-image: linear-gradient(315deg, #a7a8a8 0%, #E9EBEC 74%); + .about-media-wrapper { + display: flex; + flex-direction: row; + align-items: center; + gap: 100px; + + @media (max-width: 768px) { + flex-direction: column; + gap: 20px; + } + } + + .about-video-left { + flex: 1; + min-width: 0; + position: relative; + + video { + width: 100%; + max-width: 600px; + height: auto; + display: block; + border-radius: 8px; + box-shadow: 0 5px 15px rgba(0,0,0,0.1); + } + .sound-toggle { + position: absolute; + bottom: 15px; + right: 15px; + background: rgba(0,0,0,0.5); + border: none; + color: white; + border-radius: 50%; + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + z-index: 10; + font-size: 14px; + } + + } + + .about-text-right { + flex: 1; + min-width: 0; + } + + .about-container { + max-width: 800px; + } + } + +.about-second { + background-color: #fff8e6; + min-height: 100vh; + display: flex; + align-items: center; + padding: 40px 0; + + .about-container { + width: 100%; + max-width: 1400px; + margin: 0 auto; + padding: 0 20px; + display: flex; + flex-direction: column; + + @media (min-width: 992px) { + flex-direction: row; + gap: 50px; + padding: 0 40px; + } + + .about-left-img { + width: 100%; + margin-bottom: 30px; + + @media (min-width: 992px) { + flex: 0 0 45%; + margin-bottom: 0; + align-self: center; + } + + .image-slider { + position: relative; + width: 100%; + height: 300px; // Mobile first + overflow: hidden; + border-radius: 12px; + box-shadow: 0 8px 24px rgba(0,0,0,0.12); + + @media (min-width: 768px) { + height: 400px; + } + + @media (min-width: 992px) { + height: 500px; + } + + img { + position: absolute; + width: 100%; + height: 100%; + object-fit: cover; + opacity: 0; + transition: opacity 0.8s cubic-bezier(0.4, 0, 0.2, 1); + + &.active { + opacity: 1; + } + } + } + } + + .about-right-content { + width: 100%; + + @media (min-width: 992px) { + flex: 0 0 50%; + align-self: center; + } + + h2 { + margin: 0 0 25px; + font-size: 2.2rem; + line-height: 1.2; + color: #2c3e50; + + @media (min-width: 768px) { + font-size: 2.8rem; + } + + @media (min-width: 992px) { + margin: 0 0 30px; + font-size: 3.2rem; + } + } + + p { + font-size: 1.1rem; + line-height: 1.7; + margin-bottom: 25px; + + @media (min-width: 768px) { + font-size: 1.2rem; + } + } + + .about-cta { + display: inline-block; + margin-top: 20px; + } + } + } +} + +.about-third { + background-color: #e6f5fc; + min-height: 100vh; + display: flex; + align-items: center; + padding: 40px 0; + + .about-container { + width: 100%; + max-width: 1400px; + margin: 0 auto; + padding: 0 20px; + display: flex; + flex-direction: column; + + @media (min-width: 992px) { + flex-direction: row; + gap: 50px; + padding: 0 40px; + } + + .about-right-img { + flex-basis: 100%; + margin-bottom: 30px; + + @media (min-width: 992px) { + flex: 0 0 45%; + margin-bottom: 0; + align-self: center; + } + + .image-slider { + position: relative; + width: 100%; + height: 300px; + overflow: hidden; + border-radius: 12px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); + + @media (min-width: 768px) { + height: 400px; + } + + @media (min-width: 992px) { + height: 500px; + } + + img { + position: absolute; + width: 100%; + height: 100%; + object-fit: cover; + opacity: 0; + transition: opacity 0.8s cubic-bezier(0.4, 0, 0.2, 1); + + &.active { + opacity: 1; + } + } + } + } + + .about-left-content { + flex-basis: 100%; + + @media (min-width: 992px) { + flex: 0 0 50%; + align-self: center; + } + + h2 { + margin: 0 0 25px; + font-size: 2.2rem; + line-height: 1.2; + color: #2c3e50; + + @media (min-width: 768px) { + font-size: 2.8rem; + } + + @media (min-width: 992px) { + margin: 0 0 30px; + font-size: 3.2rem; + } + } + + p { + font-size: 1.1rem; + line-height: 1.7; + margin-bottom: 25px; + + @media (min-width: 768px) { + font-size: 1.2rem; + } + } + + .about-cta { + display: inline-block; + margin-top: 20px; + } + } + } +} + + + + + .about-four { + background-color: #FFFFFF; + h2 { + margin: 0 0 25px; + font-size: 2.2rem; + line-height: 1.2; + color: #2c3e50; + text-align: center; + + @media (min-width: 768px) { + font-size: 2.8rem; + } + + @media (min-width: 992px) { + margin: 0 0 30px; + font-size: 3.2rem; + } + } + .about-container { + display: block; + @media (min-width: 768px) { + display: flex; + justify-content: space-between; + } + + + .about-member { + background-color: #c0c6fa; + padding: 20px; + margin: 0 0 60px; + border-radius: 5px; + box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; + @media (min-width: 768px) { + flex-basis: 31%; + display: flex; + flex-direction: column; + justify-content: space-between; + } + + h3 { + @media (min-width: 768px) { + font-size: 32px; + } + } + + .about-social { + display: flex; + justify-content: flex-start; + + a img { + border: none; + max-width: 40px; + } + } + } + } + } + .about-six { + .about-container { + text-align: center; + max-width: 800px; + } + } + +} +.carousel-container { + display: flex; + align-items: center; + justify-content: center; + position: relative; + overflow: hidden; + max-width: 100%; + padding: 1rem; + + .nav { + background-color: transparent; + border: none; + font-size: 2rem; + cursor: pointer; + color: #357; + z-index: 10; + padding: 0 1rem; + } + + .carousel-slide { + display: flex; + gap: 2rem; + transition: transform 0.5s ease; + } + + .about-member { + flex: 0 0 300px; + text-align: center; + border-radius: 10px; + box-shadow: 0 0 10px rgba(0,0,0,0.1); + background-color: #fff; + padding: 1rem; + + img { + width: 100%; + height: auto; + border-radius: 10px; + max-height: 300px; + object-fit: cover; + } + + h3 { + margin: 0.5rem 0 0.2rem; + font-size: 1.2rem; + } + + p { + color: #555; + font-style: italic; + } + } +} diff --git a/src/app/about-us/about-us.component.spec.ts b/src/app/about-us/about-us.component.spec.ts new file mode 100644 index 000000000..fbc856a81 --- /dev/null +++ b/src/app/about-us/about-us.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AboutUsComponent } from './about-us.component'; + +describe('AboutUsComponent', () => { + let component: AboutUsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AboutUsComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(AboutUsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/about-us/about-us.component.ts b/src/app/about-us/about-us.component.ts new file mode 100644 index 000000000..646e1a56c --- /dev/null +++ b/src/app/about-us/about-us.component.ts @@ -0,0 +1,168 @@ +import { AfterViewInit, Component, OnDestroy, OnInit, ElementRef, ViewChild } from '@angular/core'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-about-us', + templateUrl: './about-us.component.html', + styleUrls: ['./about-us.component.scss'] +}) +export class AboutUsComponent implements OnInit, AfterViewInit, OnDestroy { + constructor(private router: Router) {} + + @ViewChild('aboutVideo', { static: false }) aboutVideo!: ElementRef; + + EventImages = [ + { src: '/assets/img/FITA/FITA1.jfif', alt: 'Event 1', active: true }, + { src: '/assets/img/FITA/FITA2.jfif', alt: 'Event 2', active: false }, + { src: '/assets/img/FITA/FITA3.jfif', alt: 'Event 3', active: false }, + { src: '/assets/img/FITA/FITA4.jfif', alt: 'Event 4', active: false }, + { src: '/assets/img/FITA/FITA5.jfif', alt: 'Event 5', active: false }, + { src: '/assets/img/FITA/FITA6.jfif', alt: 'Event 6', active: false }, + { src: '/assets/img/FITA/FITA7.jfif', alt: 'Event 7', active: false } + ]; + + TeamBuildingImages = [ + { src: '/assets/img/TeamBuilding/TM1.jpeg', alt: 'TM 1', active: true }, + { src: '/assets/img/TeamBuilding/TM2.jpeg', alt: 'TM 2', active: false }, + { src: '/assets/img/TeamBuilding/TM3.jpeg', alt: 'TM 3', active: false }, + { src: '/assets/img/TeamBuilding/TM4.jpeg', alt: 'TM 4', active: false }, + { src: '/assets/img/TeamBuilding/TM5.jpeg', alt: 'TM 5', active: false }, + { src: '/assets/img/TeamBuilding/TM6.jpeg', alt: 'TM 6', active: false }, + { src: '/assets/img/TeamBuilding/TM7.jpeg', alt: 'TM 7', active: false }, + { src: '/assets/img/TeamBuilding/TM8.jpeg', alt: 'TM 8', active: false } + ]; + + membres = [ + { nom: 'Najet', prenom: 'Boukadi', profession: 'Financial Manager', image: '/assets/img/Equipe/NajetBoukadi.jfif' }, + { nom: 'Anis', prenom: 'Kalel', profession: 'PDG', image: '/assets/img/Equipe/AnisKallel.jfif' }, + { nom: 'Mariem', prenom: 'Ayari', profession: 'Marketing Mnager', image: '/assets/img/Equipe/MariemAyari.jpeg' }, + { nom: 'Skander', prenom: 'Elj', profession: 'Information System Mnager', image: '/assets/img/Equipe/SkanderElj.jfif' }, + { nom: 'Chawki', prenom: 'Zorgui', profession: 'Chef Service Vente et Aprés vente', image: '/assets/img/Equipe/ChawkiZorgui.jpeg' }, + { nom: 'Sarra', prenom: 'Dabbebi', profession: 'Responsable RH', image: '/assets/img/Equipe/SarraDabbebi.jpeg' }, + { nom: 'Marwa', prenom: 'Henchir', profession: 'Cheffe de projet IT', image: '/assets/img/Equipe/MarwaHenchir.jpeg' }, + + ]; + + slideConfig = { + slidesToShow: 3, + slidesToScroll: 3, + dots: true, + infinite: false, + arrows: true + }; + + currentIndexEvent = 0; + currentIndexTeamBuilding = 0; + + slideIntervalEvent: any; + slideIntervalTeamBuilding: any; + + private videoObserver?: IntersectionObserver; + + visibleMembres: any[] = []; + membreIndex: number = 0; + membresParSlide: number = 3; + + ngOnInit(): void { + this.startSlider(this.EventImages, 'event'); + this.startSlider(this.TeamBuildingImages, 'teamBuilding'); + this.updateVisibleMembres(); + } + + ngAfterViewInit(): void { + const videoEl = this.aboutVideo?.nativeElement; + if (!videoEl) return; + + videoEl.muted = true; + videoEl.playsInline = true; + videoEl.play().catch(err => console.warn('Autoplay failed:', err)); + + this.videoObserver = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + videoEl.play().catch(err => console.warn('Autoplay failed:', err)); + } else { + videoEl.pause(); + } + }, + { threshold: 0.1 } + ); + + this.videoObserver.observe(videoEl); + } + + startSlider(tab: any[], sliderType: 'event' | 'teamBuilding'): void { + const interval = setInterval(() => { + this.nextSlide(tab, sliderType); + }, 3000); + + if (sliderType === 'event') { + this.slideIntervalEvent = interval; + } else { + this.slideIntervalTeamBuilding = interval; + } + } + + nextSlide(tab: any[], sliderType: 'event' | 'teamBuilding'): void { + let currentIndex = sliderType === 'event' ? this.currentIndexEvent : this.currentIndexTeamBuilding; + + tab[currentIndex].active = false; + currentIndex = (currentIndex + 1) % tab.length; + tab[currentIndex].active = true; + + if (sliderType === 'event') { + this.currentIndexEvent = currentIndex; + } else { + this.currentIndexTeamBuilding = currentIndex; + } + } + + // === Fonctions carrousel membres === + updateVisibleMembres(): void { + this.visibleMembres = this.membres.slice(this.membreIndex, this.membreIndex + this.membresParSlide); + } + + nextMembre(): void { + if (this.membreIndex + this.membresParSlide < this.membres.length) { + this.membreIndex++; + this.updateVisibleMembres(); + } + } + + prevMembre(): void { + if (this.membreIndex > 0) { + this.membreIndex--; + this.updateVisibleMembres(); + } + } + + ngOnDestroy(): void { + if (this.slideIntervalEvent) { + clearInterval(this.slideIntervalEvent); + } + if (this.slideIntervalTeamBuilding) { + clearInterval(this.slideIntervalTeamBuilding); + } + if (this.videoObserver && this.aboutVideo) { + this.videoObserver.unobserve(this.aboutVideo.nativeElement); + this.videoObserver.disconnect(); + } + } + + soundOn = false; + + toggleSound(): void { + const videoEl = this.aboutVideo.nativeElement; + this.soundOn = !this.soundOn; + videoEl.muted = !this.soundOn; + + if (this.soundOn) { + videoEl.play().catch(err => console.warn('Play with sound failed:', err)); + } + } + + contact(event: Event) { + event.preventDefault(); + this.router.navigate(['contact']); + } +} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 21d81f5ed..08e182e00 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,18 +1,51 @@ -import { Component, OnInit } from '@angular/core'; +import { AfterViewInit, Component, OnInit } from '@angular/core'; import { LocationStrategy, PlatformLocation, Location } from '@angular/common'; - -@Component({ +import { Router ,NavigationEnd} from '@angular/router'; +import { ScrollService } from './Services/scroll.service'; +import { filter } from 'rxjs/operators'; +import { LanguageService } from './Services/language.service'; +import { TranslateService } from '@ngx-translate/core';@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) -export class AppComponent implements OnInit { +export class AppComponent implements OnInit,AfterViewInit { + constructor(private translate: TranslateService, + public location: Location,private router: Router, + private scrollService: ScrollService, + private languageService: LanguageService) + { + translate.setDefaultLang('en'); + + const savedLang = localStorage.getItem('language') || 'en'; + + console.log('Langue détectée au démarrage :', savedLang); - constructor(public location: Location) {} + translate.use(savedLang).subscribe({ + next: () => console.log(`Langue ${savedLang} chargée avec succès`), + error: (err) => console.error(`Erreur lors du chargement de la langue ${savedLang}`, err) + }); + + + } ngOnInit(){ } - + ngAfterViewInit(): void { + this.router.events + .pipe(filter(event => event instanceof NavigationEnd)) + .subscribe(() => { + const anchor = this.scrollService.getAnchor(); + if (anchor) { + setTimeout(() => { + const element = document.getElementById(anchor); + if (element) { + element.scrollIntoView({ behavior: 'smooth' }); + } + }, 100); + } + }); + } isMap(path){ var titlee = this.location.prepareExternalUrl(this.location.path()); titlee = titlee.slice( 1 ); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 24da5d994..a31f8fffa 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,10 +1,10 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { NgModule } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { HttpClientModule } from '@angular/common/http'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { HttpClient, HttpClientModule } from '@angular/common/http'; import { RouterModule } from '@angular/router'; -import { AppRoutingModule } from './app.routing'; + import { NavbarModule } from './shared/navbar/navbar.module'; import { FooterModule } from './shared/footer/footer.module'; import { SidebarModule } from './sidebar/sidebar.module'; @@ -12,6 +12,39 @@ import { SidebarModule } from './sidebar/sidebar.module'; import { AppComponent } from './app.component'; import { AdminLayoutComponent } from './layouts/admin-layout/admin-layout.component'; +import { VitrineComponent } from './vitrine/vitrine.component'; +import { CarouselHomePageComponent } from './carousel-home-page/carousel-home-page.component'; +import { HeaderHomePageComponent } from './header-home-page/header-home-page.component'; +import { ClientsCarouselComponent } from './clients-carousel/clients-carousel.component'; +import { IOTCarouselComponent } from './iotcarousel/iotcarousel.component'; +import { RegisterComponent } from './register/register.component'; +import { FooterHomePageComponent } from './footer-home-page/footer-home-page.component'; +import { AboutUsComponent } from './about-us/about-us.component'; +import { ContactUsComponent } from './contact-us/contact-us.component'; +import { ChatBotComponent } from './chat-bot/chat-bot.component'; +import { BlogsComponent } from './blogs/blogs.component'; +import { FormProductsComponent } from './form-products/form-products.component'; +import { UsersListsComponent } from './users-lists/users-lists.component'; +import { BlogslistComponent } from './blogslist/blogslist.component'; +import { FormblogComponent } from './formblog/formblog.component'; +import { ProductsComponent } from './products/products.component'; +import { Carousel2Component } from './carousel2/carousel2.component'; +import { CarteProduitComponent } from './carte-produit/carte-produit.component'; +import { CarteProduitNodevisComponent } from './carte-produit-nodevis/carte-produit-nodevis.component'; +import { PetitCadreComponent } from './petit-cadre/petit-cadre.component'; +import { TitreproduitComponent } from './titreproduit/titreproduit.component'; +import { NavbarComponent } from './navbar/navbar.component'; +import { FormulaireIotItComponent } from './formulaire-iot-it/formulaire-iot-it.component'; +import { FormFranchiseComponent } from './form-franchise/form-franchise.component'; +import { AppRoutingModule } from './app.routing'; +import { ListeFranchisesComponent } from './liste-franchises/liste-franchises.component'; +import { FranchiseDetailComponent } from './franchise-detail/franchise-detail.component'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; + +export function HttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} @NgModule({ imports: [ @@ -22,12 +55,48 @@ import { AdminLayoutComponent } from './layouts/admin-layout/admin-layout.compon NavbarModule, FooterModule, SidebarModule, - AppRoutingModule + AppRoutingModule, + ReactiveFormsModule, + HttpClientModule, + TranslateModule.forRoot({ + defaultLanguage: 'en', + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), ], declarations: [ AppComponent, - AdminLayoutComponent - ], + AdminLayoutComponent, + VitrineComponent, + CarouselHomePageComponent, + HeaderHomePageComponent, + ClientsCarouselComponent, + IOTCarouselComponent, + RegisterComponent, + FooterHomePageComponent, + AboutUsComponent, + ContactUsComponent, + ChatBotComponent, + BlogsComponent, + FormProductsComponent, + UsersListsComponent, + BlogslistComponent, + FormblogComponent, + ProductsComponent, + Carousel2Component, + CarteProduitComponent, + CarteProduitNodevisComponent, + PetitCadreComponent, + TitreproduitComponent, + NavbarComponent, + FormulaireIotItComponent, + FormFranchiseComponent, + ListeFranchisesComponent, + FranchiseDetailComponent + ], providers: [], bootstrap: [AppComponent] }) diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts index 993dc346d..71280bd8f 100644 --- a/src/app/app.routing.ts +++ b/src/app/app.routing.ts @@ -1,12 +1,63 @@ -import { NgModule } from '@angular/core'; +import { Component, NgModule } from '@angular/core'; import { CommonModule, } from '@angular/common'; import { BrowserModule } from '@angular/platform-browser'; import { Routes, RouterModule } from '@angular/router'; import { AdminLayoutComponent } from './layouts/admin-layout/admin-layout.component'; +import { VitrineComponent } from './vitrine/vitrine.component'; +import { RegisterComponent } from './register/register.component'; +import { AboutUsComponent } from './about-us/about-us.component'; +import { ContactUsComponent } from './contact-us/contact-us.component'; +import { BlogsComponent } from './blogs/blogs.component'; +import { FormProductsComponent } from './form-products/form-products.component'; +import { TablesComponent } from './tables/tables.component'; +import { UsersListsComponent } from './users-lists/users-lists.component'; +import { BlogslistComponent } from './blogslist/blogslist.component'; +import { FormblogComponent } from './formblog/formblog.component'; +import {ProductsComponent} from './products/products.component'; +import {FormulaireIotItComponent} from './formulaire-iot-it/formulaire-iot-it.component'; +import { FormFranchiseComponent } from './form-franchise/form-franchise.component'; +import { ListeFranchisesComponent } from './liste-franchises/liste-franchises.component'; +import { FranchiseDetailComponent } from './franchise-detail/franchise-detail.component'; const routes: Routes =[ + + { path: '', component: VitrineComponent }, + { path: 'products', component: ProductsComponent }, + { path: 'home', component: VitrineComponent }, + { path: 'auth', component: RegisterComponent }, + { path: 'about', component: AboutUsComponent }, + { path: 'contact', component: ContactUsComponent }, + {path:'blogs',component:BlogsComponent}, + + { path: 'formulaireiotit', component: FormulaireIotItComponent }, + { path: 'formulairefranchise', component: FormFranchiseComponent }, { + path: 'admin', + redirectTo: 'dashboard', + pathMatch: 'full', + }, { + path: '', + component: AdminLayoutComponent, + children: [ + { + path: '', + loadChildren: () => import('./layouts/admin-layout/admin-layout.module').then(x => x.AdminLayoutModule) + }, + { path: 'add-product', component: FormProductsComponent }, + { path: 'update-product/:id', component: FormProductsComponent }, + { path: 'listProducts', component: TablesComponent }, + {path:'listblogs',component:BlogslistComponent}, + {path:'listusers',component:UsersListsComponent}, + { path: 'add-blog', component: FormblogComponent }, + { path: 'update-blog/:id', component: FormblogComponent }, + {path:'franchises',component:ListeFranchisesComponent}, + { path: 'franchises/:id', component: FranchiseDetailComponent }, + { path: 'admin', redirectTo: 'dashboard', pathMatch: 'full' }, + ] + } +] + /*{ path: '', redirectTo: 'dashboard', pathMatch: 'full', @@ -14,15 +65,25 @@ const routes: Routes =[ path: '', component: AdminLayoutComponent, children: [ - { - path: '', - loadChildren: () => import('./layouts/admin-layout/admin-layout.module').then(x => x.AdminLayoutModule) - }]}, + { + path: '', + loadChildren: () => import('./layouts/admin-layout/admin-layout.module').then(x => x.AdminLayoutModule) + }, + { path: 'add-product', component: FormProductsComponent }, + { path: 'update-product/:id', component: FormProductsComponent }, + { path: 'listProducts', component: TablesComponent }, + {path:'listblogs',component:BlogslistComponent}, + {path:'listusers',component:UsersListsComponent}, + { path: 'add-blog', component: FormblogComponent }, + { path: 'update-blog/:id', component: FormblogComponent }, + { path: 'admin', redirectTo: 'dashboard', pathMatch: 'full' }, + ] + }, { - path: '**', - redirectTo: 'dashboard' + path: '**',component: VitrineComponent + } -]; +];*/ @NgModule({ imports: [ diff --git a/src/app/blogs/blogs.component.html b/src/app/blogs/blogs.component.html new file mode 100644 index 000000000..3dd65b310 --- /dev/null +++ b/src/app/blogs/blogs.component.html @@ -0,0 +1,69 @@ + + +
+ + + + + + + + + +




+ +
+ +
+
+

{{ blog.author }}

+
+

{{ blog.author }}

+
+ +
+

{{ blog.title }}

+

{{ blog.summary }}

+
+ +
+
+ + + +
+



+ +
+ + + + + + + + + diff --git a/src/app/blogs/blogs.component.scss b/src/app/blogs/blogs.component.scss new file mode 100644 index 000000000..700e5de37 --- /dev/null +++ b/src/app/blogs/blogs.component.scss @@ -0,0 +1,213 @@ +.co-blog-component { + background: url('/assets/img/infinite-loop-01.jpg') no-repeat center center; + background-size: cover; + min-height: 100vh; + position: relative; + top:0; + margin: 0; + padding: 0; + .blog-container { + background: #fff; + border-radius: 5px; + box-shadow: hsla(0, 0, 0, .2) 0 4px 2px -2px; + font-family: "adelle-sans", sans-serif; + font-weight: 100; + width: 20rem; + margin: 0 auto 48px; + + @media screen and (min-width: 480px) { + width: 28rem; + } + @media screen and (min-width: 767px) { + width: 40rem; + } + @media screen and (min-width: 959px) { + width: 50rem; + } + } + + .blog-container a { + color: #4d4dff; + text-decoration: none; + transition: .25s ease; + + &:hover { + border-color: #ff4d4d; + color: #ff4d4d; + } + } + + .blog-cover { + background: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/17779/yosemite-3.jpg"); + background-size: cover; + border-radius: 5px 5px 0 0; + height: 15rem; + box-shadow: inset hsla(0, 0, 0, .2) 0 64px 64px 16px; + } + + .blog-author, + .blog-author--no-cover { + margin: 0 auto; + padding-top: .125rem; + width: 80%; + } + + .blog-author h3::before, + .blog-author--no-cover h3::before { + background: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/17779/russ.jpeg"); + background-size: cover; + border-radius: 50%; + content: " "; + display: inline-block; + height: 32px; + margin-right: .5rem; + position: relative; + top: 8px; + width: 32px; + } + + .blog-author h3 { + color: #fff; + font-weight: 100; + } + + .blog-author--no-cover h3 { + color: lighten(#333, 40%); + font-weight: 100; + } + + .blog-body { + margin: 0 auto; + width: 80%; + } + + .video-body { + height: 100%; + width: 100%; + } + + .blog-title h1 a { + color: #333; + font-weight: 100; + } + + .blog-summary p { + color: lighten(#333, 10%); + } + + .blog-tags ul { + display: flex; + flex-direction: row; + flex-wrap: wrap; + list-style: none; + padding-left: 0; + } + + .blog-tags li + li { + margin-left: .5rem; + } + + .blog-tags a { + border: 1px solid lighten(#333, 40%); + border-radius: 3px; + color: lighten(#333, 40%); + font-size: .75rem; + height: 1.5rem; + line-height: 1.5rem; + letter-spacing: 1px; + padding: 0 .5rem; + text-align: center; + text-transform: uppercase; + white-space: nowrap; + width: 5rem; + } + + .blog-footer { + border-top: 1px solid lighten(#333, 70%); + margin: 0 auto; + padding-bottom: .125rem; + width: 80%; + } + + .blog-footer ul { + list-style: none; + display: flex; + flex: row wrap; + justify-content: flex-end; + padding-left: 0; + } + + .blog-footer li:first-child { + margin-right: auto; + } + + .blog-footer li + li { + margin-left: .5rem; + } + + .blog-footer li { + color: lighten(#333, 40%); + font-size: .75rem; + height: 1.5rem; + letter-spacing: 1px; + line-height: 1.5rem; + text-align: center; + text-transform: uppercase; + position: relative; + white-space: nowrap; + + & a { + color: lighten(#333, 40%); + } + } + + + .published-date { + border: 1px solid lighten(#333, 40%); + border-radius: 3px; + padding: 0 .5rem; + } + + + .icon-star, + .icon-bubble { + fill: lighten(#333, 40%); + height: 35px; + margin-right: .5rem; + transition: .25s ease; + width: 35px; + + &:hover { + fill: #ff4d4d; + } + } +} +.like-button { + background: transparent; + border: none; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 16px; + padding: 0; + vertical-align: middle; +} + +.like-button svg.icon-heart { + width: 24px; + height: 24px; + transition: fill 0.3s ease; + display: block; +} + +.like-button .numero { + color: #333; + font-weight: 500; + line-height: 1; + vertical-align: middle; + margin-top: 2px; /* Ajustement fin */ + user-select: none; +} + + diff --git a/src/app/blogs/blogs.component.spec.ts b/src/app/blogs/blogs.component.spec.ts new file mode 100644 index 000000000..4803d0178 --- /dev/null +++ b/src/app/blogs/blogs.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BlogsComponent } from './blogs.component'; + +describe('BlogsComponent', () => { + let component: BlogsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ BlogsComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(BlogsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/blogs/blogs.component.ts b/src/app/blogs/blogs.component.ts new file mode 100644 index 000000000..57846d296 --- /dev/null +++ b/src/app/blogs/blogs.component.ts @@ -0,0 +1,42 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-blogs', + templateUrl: './blogs.component.html', + styleUrls: ['./blogs.component.scss'] +}) +export class BlogsComponent implements OnInit { + blogPosts = [ + { + author: 'Russ Beye', + title: 'I Like To Make Cool Things', + summary: `I love working on fresh designs that breathe. To that end, I need to freshen up my portfolio...`, + tags: ['css', 'web design', 'codepen', 'twitter'], + publishedDate: '12/01/2025', + comments: 4, + shares: 1, + likes: 0 , + liked:false, + coverImage: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/17779/yosemite-3.jpg' + }, + { + author: 'Russ Beye', + title: 'This Post Has No Cover Image', + summary: `Here is an example of a post without a cover image. You don't always have to have a cover image.`, + tags: ['design', 'web dev', 'css'], + publishedDate: '16/06/2025', + comments: 8, + liked:false, + shares: 3,likes: 10 + } + ]; + constructor() { } + + ngOnInit(): void { + } + toggleLike(index: number): void { + const blog = this.blogPosts[index]; + blog.liked = !blog.liked; + blog.likes += blog.liked ? 1 : -1; + } +} diff --git a/src/app/blogslist/blogslist.component.html b/src/app/blogslist/blogslist.component.html new file mode 100644 index 000000000..2a0fbf494 --- /dev/null +++ b/src/app/blogslist/blogslist.component.html @@ -0,0 +1,30 @@ +
+
+

Liste des Blogs

+ + + +
+
+
+
+ image blog +
+
+
{{ blog.titre }}
+

{{ blog.contenu }}

+
+
+ {{ tag.nom }} +
+
+ {{ blog.likes }} + + +
+
+
+
+
+
+
diff --git a/src/app/blogslist/blogslist.component.scss b/src/app/blogslist/blogslist.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/blogslist/blogslist.component.spec.ts b/src/app/blogslist/blogslist.component.spec.ts new file mode 100644 index 000000000..4a90aa9ab --- /dev/null +++ b/src/app/blogslist/blogslist.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BlogslistComponent } from './blogslist.component'; + +describe('BlogslistComponent', () => { + let component: BlogslistComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ BlogslistComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(BlogslistComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/blogslist/blogslist.component.ts b/src/app/blogslist/blogslist.component.ts new file mode 100644 index 000000000..33f1770df --- /dev/null +++ b/src/app/blogslist/blogslist.component.ts @@ -0,0 +1,70 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import Swal from 'sweetalert2'; +import { environment } from '../../environments/environment'; +import { BlogService } from 'app/Services/BlogService'; + +export interface Blog { + id: number; + titre: string; + contenu: string; + imagePath: string; + tags: string[]; + likes: number; +} + +@Component({ + selector: 'app-blogslist', + templateUrl: './blogslist.component.html', + styleUrls: ['./blogslist.component.scss'] +}) +export class BlogslistComponent implements OnInit { + blogs: Blog[] = []; +apiBaseUrl = environment.baseUrl; + + constructor(private router: Router, private blogService: BlogService) { } + + ngOnInit() { + this.loadBlogs(); + } + + loadBlogs() { + this.blogService.getAllBlogs().subscribe({ + next: (data) => this.blogs = data, + error: (err) => console.error('Erreur lors du chargement des blogs:', err) + }); + } + + editBlog(id: number) { + this.router.navigate(['/update-blog', id]); + console.log("Edit blog", id); + } + + confirmDelete(id: number) { + Swal.fire({ + title: 'Supprimer ce blog ?', + text: 'Cette action est irréversible.', + icon: 'warning', + showCancelButton: true, + confirmButtonText: 'Oui, supprimer', + cancelButtonText: 'Annuler' + }).then((result) => { + if (result.isConfirmed) { + this.blogService.deleteBlog(id).subscribe({ + next: () => { + this.blogs = this.blogs.filter(b => b.id !== id); + Swal.fire('Supprimé !', 'Le blog a été supprimé.', 'success'); + }, + error: (err) => { + console.error(err); + Swal.fire('Erreur', 'Une erreur est survenue lors de la suppression.', 'error'); + } + }); + } + }); +} + +getImageUrl(imagePath: string): string { + return this.apiBaseUrl + imagePath; + } +} diff --git a/src/app/carousel-home-page/carousel-home-page.component.html b/src/app/carousel-home-page/carousel-home-page.component.html new file mode 100644 index 000000000..24ff9f8c2 --- /dev/null +++ b/src/app/carousel-home-page/carousel-home-page.component.html @@ -0,0 +1,42 @@ + + +
+ + + +
diff --git a/src/app/carousel-home-page/carousel-home-page.component.scss b/src/app/carousel-home-page/carousel-home-page.component.scss new file mode 100644 index 000000000..c22475f73 --- /dev/null +++ b/src/app/carousel-home-page/carousel-home-page.component.scss @@ -0,0 +1,151 @@ + +.carousel-container { + height: 100%; + width: 100%; + +background-image: url(/assets/img/HomeCarousselBackground.png); + background-repeat: no-repeat; + background-position: center center; + background-size: cover; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + position: relative; + overflow: hidden; +} + +.carousel-slide { + display: flex; + justify-content: space-between; + align-items: center; + width: 90%; + max-width: 1400px; + padding: 40px; + box-sizing: border-box; + position: relative; + transition: opacity 1s ease-in-out; + opacity: 1; +} + +.fade { + animation: fadeEffect 1s; +} + +@keyframes fadeEffect { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.left-content { + color: white; + width: 50%; +} + +.title { + font-size: 2.8rem; + font-weight: bold; + margin: 0 0 15px; +} + +.subtitle { + font-size: 1.5rem; + color: #369; + margin-bottom: 10px; +} + +.description { + font-size: 1.1rem; + margin-bottom: 20px; + line-height: 1.5; +} + +.button-wrapper { + margin-top: 10px; +} + +.button-wrapper button { + padding: 10px 20px; + font-size: 1rem; + background-color: #007bff; + border: none; + border-radius: 6px; + color: white; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.button-wrapper button:hover { + background-color: #0056b3; +} + +.right-image { + width: 45%; + display: flex; + justify-content: center; + align-items: center; +} + +.right-image img { + width: 300px; + height: 300px; + object-fit: cover; + border-radius: 50%; + border: 5px solid #ffffff96; +} + +.dots { + position: absolute; + bottom: 40px; + display: flex; + gap: 10px; +} + +.dots span { + width: 10px; + height: 10px; + background-color: gray; + border-radius: 50%; + cursor: pointer; + transition: background-color 0.3s; +} + +.dots span.active { + background-color: white; + transform: scale(1.4); +} + +.scroll-arrow { + position: absolute; + right: 20px; + bottom: 20px; + background-color: rgba(255, 255, 255, 0.1); + border-radius: 50%; + padding: 10px; + cursor: pointer; + animation: saut 2s infinite; +} + +@keyframes saut{ + 0%,20%,50%,80%,100% { + transform:translateY(0); + } + 40%{ + transform: translateY(15px); + + } + 60%{ + transform: translateY(15px); + } +} + +.scroll-arrow img { + width: 40px; + height: 40px; +} \ No newline at end of file diff --git a/src/app/carousel-home-page/carousel-home-page.component.spec.ts b/src/app/carousel-home-page/carousel-home-page.component.spec.ts new file mode 100644 index 000000000..afccc81d9 --- /dev/null +++ b/src/app/carousel-home-page/carousel-home-page.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CarouselHomePageComponent } from './carousel-home-page.component'; + +describe('CarouselHomePageComponent', () => { + let component: CarouselHomePageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CarouselHomePageComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CarouselHomePageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/carousel-home-page/carousel-home-page.component.ts b/src/app/carousel-home-page/carousel-home-page.component.ts new file mode 100644 index 000000000..1bf1358aa --- /dev/null +++ b/src/app/carousel-home-page/carousel-home-page.component.ts @@ -0,0 +1,62 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-carousel-home-page', + templateUrl: './carousel-home-page.component.html', + styleUrls: ['./carousel-home-page.component.scss'] +}) +export class CarouselHomePageComponent implements OnInit { + + slides = [ + { + titleKey: 'CAROUSEL.SLIDE1.TITLE', + subtitleKey: 'CAROUSEL.SLIDE1.SUBTITLE', + descriptionKey: 'CAROUSEL.SLIDE1.DESCRIPTION', + image: '/assets/img/route.png' + }, + { + titleKey: 'CAROUSEL.SLIDE2.TITLE', + subtitleKey: 'CAROUSEL.SLIDE2.SUBTITLE', + descriptionKey: 'CAROUSEL.SLIDE2.DESCRIPTION', + image: '/assets/img/iot.png' + }, + { + titleKey: 'CAROUSEL.SLIDE3.TITLE', + subtitleKey: 'CAROUSEL.SLIDE3.SUBTITLE', + descriptionKey: 'CAROUSEL.SLIDE3.DESCRIPTION', + image: '/assets/img/service.png' + } + ]; + + currentSlide = 0; + intervalId: any; + + constructor(private router: Router) {} + + ngOnInit() { + this.intervalId = setInterval(() => this.nextSlide(), 4000); + } + + ngOnDestroy() { + clearInterval(this.intervalId); + } + + nextSlide() { + this.currentSlide = (this.currentSlide + 1) % this.slides.length; + } + + goToSlide(index: number) { + this.currentSlide = index; + } + + goToVitrine() { + this.router.navigate(['/vitrine']); + } + + goToHomeAndScroll(section: string, event: Event) { + event.preventDefault(); + this.router.navigate([''], { queryParams: { section } }); +} + +} diff --git a/src/app/carousel2/carousel2.component.css b/src/app/carousel2/carousel2.component.css new file mode 100644 index 000000000..20eb7effb --- /dev/null +++ b/src/app/carousel2/carousel2.component.css @@ -0,0 +1,150 @@ +.carousel-container { + width: 100%; + height: 400px; + /* background-image: url('assets/img/infinite-loop-01.jpg'); */ + background-repeat: no-repeat; + background-position: center center; + background-size: cover; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + position: relative; + overflow: hidden; +} + +.carousel-slide { + display: flex; + justify-content: space-between; + align-items: center; + width: 90%; + max-width: 1400px; + padding: 40px; + box-sizing: border-box; + position: relative; + transition: opacity 1s ease-in-out; + opacity: 1; +} + +.fade { + animation: fadeEffect 1s; +} + +@keyframes fadeEffect { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.left-content { + color: white; + width: 50%; +} + +.title { + font-size: 2.8rem; + font-weight: bold; + margin: 0 0 15px; +} + +.subtitle { + font-size: 1.5rem; + color: #00aaff; + margin-bottom: 10px; +} + +.description { + font-size: 1.1rem; + margin-bottom: 20px; + line-height: 1.5; +} + +.button-wrapper { + margin-top: 10px; +} + +.button-wrapper button { + padding: 10px 20px; + font-size: 1rem; + background-color: #007bff; + border: none; + border-radius: 6px; + color: white; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.button-wrapper button:hover { + background-color: #0056b3; +} + +.right-image { + width: 45%; + display: flex; + justify-content: center; + align-items: center; +} + +.right-image img { + width: 300px; + height: 300px; + object-fit: cover; + border-radius: 50%; + border: 5px solid #ffffff96; +} + +.dots { + position: absolute; + bottom: 40px; + display: flex; + gap: 10px; +} + +.dots span { + width: 10px; + height: 10px; + background-color: gray; + border-radius: 50%; + cursor: pointer; + transition: background-color 0.3s; +} + +.dots span.active { + background-color: white; + transform: scale(1.4); +} + +.scroll-arrow { + position: absolute; + right: 20px; + bottom: 20px; + background-color: rgba(255, 255, 255, 0.1); + border-radius: 50%; + padding: 10px; + cursor: pointer; + animation: saut 2s infinite; +} + +@keyframes saut{ + 0%,20%,50%,80%,100% { + transform:translateY(0); + } + 40%{ + transform: translateY(15px); + + } + 60%{ + transform: translateY(15px); + } +} + +.scroll-arrow img { + width: 40px; + height: 40px; +} + diff --git a/src/app/carousel2/carousel2.component.html b/src/app/carousel2/carousel2.component.html new file mode 100644 index 000000000..97ce4ee4b --- /dev/null +++ b/src/app/carousel2/carousel2.component.html @@ -0,0 +1,30 @@ + + diff --git a/src/app/carousel2/carousel2.component.spec.ts b/src/app/carousel2/carousel2.component.spec.ts new file mode 100644 index 000000000..b04d98e68 --- /dev/null +++ b/src/app/carousel2/carousel2.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Carousel2Component } from './carousel2.component'; + +describe('Carousel2Component', () => { + let component: Carousel2Component; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ Carousel2Component ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(Carousel2Component); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/carousel2/carousel2.component.ts b/src/app/carousel2/carousel2.component.ts new file mode 100644 index 000000000..1dde1d937 --- /dev/null +++ b/src/app/carousel2/carousel2.component.ts @@ -0,0 +1,62 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-carousel2', + templateUrl: './carousel2.component.html', + styleUrls: ['./carousel2.component.css'] +}) +export class Carousel2Component implements OnInit, OnDestroy { + slides = [ + { + title: 'TRACKING GPS', + subtitle: "Accélérez votre transformation digitale avec le suivi GPS", + description: "Nous accompagnons les entreprises et les particuliers avec des solutions intelligentes de suivi GPS et de gestion de flotte, conçues pour optimiser leurs performances.", + image: 'assets/img/route.png' + }, + { + title: 'SOLUTION IOT', + subtitle: "Sécurisez vos données avec les solutions IoT avancées", + description: "Nos solutions IoT vous offrent une surveillance intelligente, une gestion simplifiée de vos équipements et une sécurité renforcée, pour gagner en efficacité au quotidien", + image: 'assets/img/iot.png' + }, + { + title: 'SOLUTION IT', + subtitle: "Transformez votre entreprise grâce à la gestion de flotte", + description: "Nous développons et intégrons des solutions IT qui renforcent les opérations,améliorent la performance et accélèrent la transformation digitale des entreprises.", + image: 'assets/img/service.png' + } + ]; + + currentSlide = 0; + intervalId: any; + + constructor(private router: Router) {} + + ngOnInit() { + this.intervalId = setInterval(() => this.nextSlide(), 4000); + } + + ngOnDestroy() { + clearInterval(this.intervalId); + } + + nextSlide() { + this.currentSlide = (this.currentSlide + 1) % this.slides.length; + } + + goToSlide(index: number) { + this.currentSlide = index; + } + + goToVitrine() { + this.router.navigate(['/vitrine']); + } + scrollToGps() { + const titreSection = document.getElementById('partietitre'); + if (titreSection) { + titreSection.scrollIntoView({ behavior: 'smooth' }); + } + } + +} diff --git a/src/app/carte-produit-nodevis/carte-produit-nodevis.component.css b/src/app/carte-produit-nodevis/carte-produit-nodevis.component.css new file mode 100644 index 000000000..e38a0d3bd --- /dev/null +++ b/src/app/carte-produit-nodevis/carte-produit-nodevis.component.css @@ -0,0 +1,297 @@ +.carte-produit { + background-color: #e9ecef; + position: relative; + width: 300px; + height: 360px; + border-radius: 12px; + overflow: hidden; + box-shadow: 0px 10px 1px #38B; + cursor: pointer; + transition: transform 0.25s ease-in-out 0s; +} + +.carte-produit:hover { + transform: scale(1.02); + transform: translateY(-3px); +} + +.image-fond { + width: 100%; + height: 90%; + object-fit: contain; + position: relative; + margin-top: 10px; + +} + +.infos-base { + position: absolute; + /* bottom: 0px; */ + left: 20px; + color: white; +} + +.categorie { + color: #01b4ff; + font-weight: bold; + font-size: 14px; + display: block; + top: 50px; +} + +.titre { + color: #01b4ff; + font-size: 14px; + font-weight: bold; + display: block; + margin-bottom: 20px; +} + +.overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.50); + color: white; + padding: 20px; + display: flex; + align-items: flex-end; + box-sizing: border-box; +} + +.infos-hover h3 { + margin: 0 0 10px; + font-size: 18px; +} + +.infos-hover p { + font-size: 14px; + margin-bottom: 15px; +} + + + +.liens { + display: flex; + gap: 12px; +} + +.voir-details { + color: white; + text-decoration: none; + font-weight: bold; + border-bottom: 3px solid white; + font-size: 14px; +} +/* //////////////// */ +.modal-backdrop { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(3px); + display: flex; + justify-content: center; + align-items: center; + z-index: 999; +} + +.modal-container { + border-radius: 20px; + background-color: white; + width: 55%; + overflow-y: auto; + position: relative; + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.25); + animation: fadeIn 0.3s ease-in-out; +} + +.modal-top { + background-image: url('assets/img/infinite-loop-01.jpg'); + background-repeat: no-repeat; + background-size: cover; + /* background-position: center ; */ + /* background-color: #38B; */ + padding: 30px; + color: #ffffff; + height: 160px; +} + +.categorie h5 { + margin: 0; + font-size: 16px; + background: #fff; + color: #38B; + display: inline-block; + padding: 4px 10px; + border-radius: 10px; + font-weight: bold; + +} + + +.titre-et-description h2 { + font-size: 30px; + font-weight: bold; + /* margin: 10px 0 8px; */ + color: #fff; +} + +.titre-et-description p { + margin: 0; + font-size: 10px; + color: #fff; +} + +.modal-image { + width: 100%; + height: auto; + margin-top: 20px; + max-height: 300px; + object-fit: contain; + border-radius: 10px; + margin-bottom: 20px; +} + +.modal-content { + padding: 10px; + max-height: 90vh; +} + +.modal-content h2 { + font-size: 24px; + margin-bottom: 10px; + color: #041c44; +} + +.modal-content p { + font-size: 16px; + margin-bottom: 20px; + color: #333; +} + +.modal-bottom { + display: flex; + justify-content: space-between; + gap: 20px; +} +.imagee{ + /* background-color: #f2f2f2; */ + padding: 12px 16px; + border-radius: 10px; + width: 65%; +} +.caracteristiques-et-tarif{ + display: flex; + flex-direction: column; + gap:20px; + max-width: 50%; + min-width: 40%; +} +.caracteristiques{ + height: 60%; + background-color: #f2f2f2; + padding: 12px 16px; + border-radius: 10px; + word-wrap: break-word; + overflow-wrap: break-word; + text-align: left; +} +.tarif{ + height: auto; + background-color: #f2f2f2; + padding: 12px 16px; + border-radius: 10px; + word-wrap: break-word; + overflow-wrap: break-word; +} + +.caracteristiques h4, +.tarif h4 { + margin-top: 0; + margin-bottom: 8px; + color: #38B; + font-size: 18px; + text-decoration: underline; + font-weight: bold; + text-align: left; +} +.tarif h2 { + font-weight: bold; + color: #000000; + display: block; + text-align: left; +} + +.caracteristiques ul { + padding-left: 20px; + margin: 0; +} +.caracteristiques li{ + /* font-weight: bold; */ + color: black; +} + +.tarif p { + font-weight: bold; + font-size: 20px; + margin: 10px 0; + color: orange; +} + +.btn-devis { + background-color: #01b4ff; + color: white; + padding: 8px 16px; + border-radius: 6px; + border: none; + cursor: pointer; + font-weight: bold; +} + +.modal-close { + color: #ffffff; + position: absolute; + top: 0px; + right: 16px; + font-size: 40px; + background: none; + border: none; + cursor: pointer; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: scale(0.9); + } + to { + opacity: 1; + transform: scale(1); + } +} +.btn-panier { + margin-top: 20px; + background-color: #01b4ff; + width: 100%; + color: white; + border: none; + padding: 10px 20px; + border-radius: 8px; + font-weight: bold; + cursor: pointer; + font-size: 14px; + transition: background-color 0.2s ease-in-out; + text-align: center; +} + +.btn-panier:hover { + background-color: #008fcc; +} + + + diff --git a/src/app/carte-produit-nodevis/carte-produit-nodevis.component.html b/src/app/carte-produit-nodevis/carte-produit-nodevis.component.html new file mode 100644 index 000000000..ad9252caf --- /dev/null +++ b/src/app/carte-produit-nodevis/carte-produit-nodevis.component.html @@ -0,0 +1,62 @@ + +
+ + +
+ +

{{ titre }}

+
+ +
+
+

{{ titre }}

+

{{ description }}

+ +
+
+
+ + + + + diff --git a/src/app/carte-produit-nodevis/carte-produit-nodevis.component.spec.ts b/src/app/carte-produit-nodevis/carte-produit-nodevis.component.spec.ts new file mode 100644 index 000000000..f5acb117d --- /dev/null +++ b/src/app/carte-produit-nodevis/carte-produit-nodevis.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CarteProduitNodevisComponent } from './carte-produit-nodevis.component'; + +describe('CarteProduitNodevisComponent', () => { + let component: CarteProduitNodevisComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CarteProduitNodevisComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CarteProduitNodevisComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/carte-produit-nodevis/carte-produit-nodevis.component.ts b/src/app/carte-produit-nodevis/carte-produit-nodevis.component.ts new file mode 100644 index 000000000..cd0de2c79 --- /dev/null +++ b/src/app/carte-produit-nodevis/carte-produit-nodevis.component.ts @@ -0,0 +1,37 @@ +import { Component, Input } from '@angular/core'; +import { environment } from 'environments/environment'; + +@Component({ + selector: 'app-carte-produit-nodevis', + templateUrl: './carte-produit-nodevis.component.html', + styleUrls: ['./carte-produit-nodevis.component.css'] +}) +export class CarteProduitNodevisComponent { + @Input() titre: string = ''; + @Input() description: string = ''; + @Input() image: string = ''; + @Input() categorie: string = ''; + @Input() prix: string = ''; + @Input() caracteristiques: string[] = []; + + apiBaseUrl = environment.baseUrl + + + hover: boolean = false; + showModal: boolean = false; + + openModal() { + this.showModal = true; + } + + closeModal() { + this.showModal = false; + } + getImageUrl(imagePath: string): string { + if (imagePath.startsWith('/assets')) { + return imagePath; + } + return this.apiBaseUrl + imagePath; +} + +} diff --git a/src/app/carte-produit/carte-produit.component.css b/src/app/carte-produit/carte-produit.component.css new file mode 100644 index 000000000..61ca99a2a --- /dev/null +++ b/src/app/carte-produit/carte-produit.component.css @@ -0,0 +1,293 @@ +.carte-produit { + background-color: #e9ecef; + position: relative; + width: 300px; + height: 360px; + border-radius: 12px; + overflow: hidden; + box-shadow: 0px 10px 1px #38B; + cursor: pointer; + transition: transform 0.25s ease-in-out 0s; +} + +.carte-produit:hover { + transform: scale(1.02); + transform: translateY(-3px); +} + +.image-fond { + width: 100%; + height: 90%; + object-fit: contain; + position: relative; + margin-top: 10px; +} + +.infos-base { + position: absolute; + left: 20px; + color: white; +} + +.categorie { + display: inline-block; + background-color: white; + color: #01b4ff; + font-weight: bold; + font-size: 10px; + padding: 5px 10px; + border-radius: 8px; + margin-bottom: 10px; +} + +.titre { + color: #01b4ff; + font-size: 14px; + font-weight: bold; + display: block; + margin-bottom: 20px; +} + +.overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.50); + color: white; + padding: 20px; + display: flex; + align-items: flex-end; + box-sizing: border-box; +} + + +.infos-hover h3 { + margin: 0 0 10px; + font-size: 18px; +} + +.infos-hover p { + font-size: 14px; + margin-bottom: 15px; +} + +.btn-devis { + background-color: #01b4ff; + color: white; + border: none; + padding: 8px 14px; + border-radius: 4px; + font-size: 12px; + margin-bottom: 12px; + cursor: pointer; +} + +.liens { + display: flex; + gap: 12px; +} + +.voir-details { + color: white; + text-decoration: none; + font-weight: bold; + border-bottom: 3px solid white; + font-size: 14px; +} + +.demander-devis { + color: orange; + text-decoration: none; + font-weight: bold; + border-bottom: 3px solid orange; + font-size: 14px; +} +/* /////////////////////////// */ +.modal-backdrop { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(3px); + display: flex; + justify-content: center; + align-items: center; + z-index: 999; +} + +.modal-container { + border-radius: 20px; + background-color: white; + width: 55%; + overflow-y: auto; + position: relative; + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.25); + animation: fadeIn 0.3s ease-in-out; +} + +.modal-top { + background-image: url('assets/img/infinite-loop-01.jpg'); + background-repeat: no-repeat; + background-size: cover; + /* background-position: center ; */ + /* background-color: #38B; */ + padding: 30px; + color: #ffffff; + height: 160px; +} + +.categorie h5 { + margin: 0; + font-size: 16px; + background: #fff; + color: #38B; + display: inline-block; + padding: 4px 10px; + border-radius: 10px; + font-weight: bold; +} + +.titre-et-description h2 { + font-size: 30px; + font-weight: bold; + /* margin: 10px 0 8px; */ + color: #fff; +} + +.titre-et-description p { + margin: 0; + font-size: 10px; + color: #fff; +} + +.modal-image { + width: 100%; + height: auto; + margin-top: 20px; + max-height: 300px; + object-fit: contain; + border-radius: 10px; + margin-bottom: 20px; +} + +.modal-content { + padding: 10px; + max-height: 90vh; +} + +.modal-content h2 { + font-size: 24px; + margin-bottom: 10px; + color: #041c44; +} + +.modal-content p { + font-size: 16px; + margin-bottom: 20px; + color: #333; +} + +.modal-bottom { + display: flex; + justify-content: space-between; + gap: 20px; +} +.imagee{ + /* background-color: #f2f2f2; */ + padding: 12px 16px; + border-radius: 10px; + width: 65%; +} +.caracteristiques-et-tarif{ + display: flex; + flex-direction: column; + gap:20px; + max-width: 50%; +} + +.caracteristiques { + height: 60%; + background-color: #f2f2f2; + padding: 12px 16px; + border-radius: 10px; + word-wrap: break-word; + overflow-wrap: break-word; + text-align: left; +} +.tarif{ + height: auto; + background-color: #f2f2f2; + padding: 12px 16px; + border-radius: 10px; + word-wrap: break-word; + overflow-wrap: break-word; +} + +.caracteristiques h4, +.tarif h4 { + margin-top: 0; + margin-bottom: 8px; + color: #38B; + font-size: 18px; + text-decoration: underline; + font-weight: bold; + text-align: left; +} +.tarif h2 { + font-weight: bold; + color: #000000; + display: block; + text-align: left; +} + +.caracteristiques ul { + padding-left: 20px; + margin: 0; +} +.caracteristiques li{ + /* font-weight: bold; */ + color: black; +} + +.tarif p { + font-weight: bold; + font-size: 20px; + margin: 10px 0; + color: orange; +} + +.btn-devis { + background-color: #01b4ff; + color: white; + padding: 8px 16px; + border-radius: 6px; + border: none; + cursor: pointer; + font-weight: bold; +} + +.modal-close { + color: #ffffff; + position: absolute; + top: 0px; + right: 16px; + font-size: 40px; + background: none; + border: none; + cursor: pointer; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: scale(0.9); + } + to { + opacity: 1; + transform: scale(1); + } +} diff --git a/src/app/carte-produit/carte-produit.component.html b/src/app/carte-produit/carte-produit.component.html new file mode 100644 index 000000000..df5187cf7 --- /dev/null +++ b/src/app/carte-produit/carte-produit.component.html @@ -0,0 +1,65 @@ + + +
+ + +
+

{{ titre }}

+
+ +
+
+

{{ titre }}

+

{{ description }}

+ + +
+
+
+ + + + + diff --git a/src/app/carte-produit/carte-produit.component.spec.ts b/src/app/carte-produit/carte-produit.component.spec.ts new file mode 100644 index 000000000..c7cb1edae --- /dev/null +++ b/src/app/carte-produit/carte-produit.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CarteProduitComponent } from './carte-produit.component'; + +describe('CarteProduitComponent', () => { + let component: CarteProduitComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CarteProduitComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CarteProduitComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/carte-produit/carte-produit.component.ts b/src/app/carte-produit/carte-produit.component.ts new file mode 100644 index 000000000..112fd2d49 --- /dev/null +++ b/src/app/carte-produit/carte-produit.component.ts @@ -0,0 +1,43 @@ +import { Component, Input } from '@angular/core'; +import { Router } from '@angular/router'; +import { environment } from 'environments/environment'; + +@Component({ + selector: 'app-carte-produit', + templateUrl: './carte-produit.component.html', + styleUrls: ['./carte-produit.component.css'] +}) +export class CarteProduitComponent { + @Input() titre: string = ''; + @Input() description: string = ''; + @Input() image: string = ''; + @Input() categorie: string = ''; + @Input() caracteristiques: string[] = []; + + apiBaseUrl = environment.baseUrl + + constructor(private router: Router) {} + + hover: boolean = false; + showModal: boolean = false; + + openModal() { + this.showModal = true; + } + + closeModal() { + this.showModal = false; + } + allerAuFormulaire() { + this.closeModal(); + this.router.navigate(['/formulaireiotit']); + } + getImageUrl(imagePath: string): string { + if (imagePath.startsWith('/assets')) { + return imagePath; + } + return this.apiBaseUrl + imagePath; +} + +} + diff --git a/src/app/chat-bot/chat-bot.component.html b/src/app/chat-bot/chat-bot.component.html new file mode 100644 index 000000000..a0b7771cf --- /dev/null +++ b/src/app/chat-bot/chat-bot.component.html @@ -0,0 +1,2 @@ +
+
\ No newline at end of file diff --git a/src/app/chat-bot/chat-bot.component.scss b/src/app/chat-bot/chat-bot.component.scss new file mode 100644 index 000000000..340e2eebf --- /dev/null +++ b/src/app/chat-bot/chat-bot.component.scss @@ -0,0 +1,36 @@ +.chatbot-fab { + position: fixed; + bottom: 30px; + right: 30px; + display: flex; + align-items: center; + gap: 10px; + z-index: 1000; +} + +.chatbot-label { + background-color: #007bff; + color: white; + padding: 8px 12px; + border-radius: 20px; + font-size: 14px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); + animation: fadeIn 0.5s ease; +} + +.chatbot-button { + background-color: black; + color: white; + border: none; + border-radius: 50%; + width: 55px; + height: 55px; + font-size: 20px; + cursor: pointer; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} diff --git a/src/app/chat-bot/chat-bot.component.spec.ts b/src/app/chat-bot/chat-bot.component.spec.ts new file mode 100644 index 000000000..2b9bd93d1 --- /dev/null +++ b/src/app/chat-bot/chat-bot.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ChatBotComponent } from './chat-bot.component'; + +describe('ChatBotComponent', () => { + let component: ChatBotComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ChatBotComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ChatBotComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/chat-bot/chat-bot.component.ts b/src/app/chat-bot/chat-bot.component.ts new file mode 100644 index 000000000..6d9e88e39 --- /dev/null +++ b/src/app/chat-bot/chat-bot.component.ts @@ -0,0 +1,58 @@ +import { Component, OnInit ,Renderer2} from '@angular/core'; + +@Component({ + selector: 'app-chat-bot', + templateUrl: './chat-bot.component.html', + styleUrls: ['./chat-bot.component.scss'] +}) +export class ChatBotComponent implements OnInit { + showChat = false; + + constructor(private renderer: Renderer2) {} + + ngOnInit(): void { + const script = this.renderer.createElement('script'); + script.innerHTML = ` + (function(){ + if(!window.chatbase || window.chatbase("getState") !== "initialized") { + window.chatbase=(...arguments)=>{ + if(!window.chatbase.q){ + window.chatbase.q=[] + } + window.chatbase.q.push(arguments) + }; + window.chatbase=new Proxy(window.chatbase,{ + get(target,prop){ + if(prop==="q"){ return target.q } + return (...args)=>target(prop,...args) + } + }) + } + + const onLoad=function(){ + const script=document.createElement("script"); + script.src="https://www.chatbase.co/embed.min.js"; + script.id="f0d3oyMrl3n7digplpnOD"; + script.setAttribute("chatbotId", "f0d3oyMrl3n7digplpnOD"); + document.body.appendChild(script); + }; + + if(document.readyState==="complete"){ + onLoad() + }else{ + window.addEventListener("load",onLoad) + } + })(); + `; + this.renderer.appendChild(document.body, script); + } + + toggleChat(): void { + const iframe = document.querySelector('iframe[src*="chatbase"]') as HTMLElement; + if (iframe) { + this.showChat = !this.showChat; + iframe.style.display = this.showChat ? 'block' : 'none'; + } + } + +} diff --git a/src/app/clients-carousel/clients-carousel.component.html b/src/app/clients-carousel/clients-carousel.component.html new file mode 100644 index 000000000..15da3f632 --- /dev/null +++ b/src/app/clients-carousel/clients-carousel.component.html @@ -0,0 +1,23 @@ +
+
+

{{ 'CLIENTS.TITLE' | translate }}

+

+ {{ 'CLIENTS.DESCRIPTION' | translate }} +

+
+
+
+
+ +
+
+
+
\ No newline at end of file diff --git a/src/app/clients-carousel/clients-carousel.component.scss b/src/app/clients-carousel/clients-carousel.component.scss new file mode 100644 index 000000000..03e4a4e34 --- /dev/null +++ b/src/app/clients-carousel/clients-carousel.component.scss @@ -0,0 +1,20 @@ +.carousel-container { + width: 100%; + margin: 0 auto; +} + +.carousel-content { + display: flex; + transition: transform 0.01s linear; /* Transition très courte pour fluidité */ + will-change: transform; /* Optimisation performance */ +} + +/* Masquer la scrollbar */ +.carousel-container::-webkit-scrollbar { + display: none; +} + +.carousel-container { + -ms-overflow-style: none; + scrollbar-width: none; +} \ No newline at end of file diff --git a/src/app/clients-carousel/clients-carousel.component.spec.ts b/src/app/clients-carousel/clients-carousel.component.spec.ts new file mode 100644 index 000000000..cb45a9bb0 --- /dev/null +++ b/src/app/clients-carousel/clients-carousel.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ClientsCarouselComponent } from './clients-carousel.component'; + +describe('ClientsCarouselComponent', () => { + let component: ClientsCarouselComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ClientsCarouselComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ClientsCarouselComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/clients-carousel/clients-carousel.component.ts b/src/app/clients-carousel/clients-carousel.component.ts new file mode 100644 index 000000000..d041117bb --- /dev/null +++ b/src/app/clients-carousel/clients-carousel.component.ts @@ -0,0 +1,58 @@ +import { Component, OnInit,OnDestroy } from '@angular/core'; + +@Component({ + selector: 'app-clients-carousel', + templateUrl: './clients-carousel.component.html', + styleUrls: ['./clients-carousel.component.scss'] +}) +export class ClientsCarouselComponent implements OnInit , OnDestroy { + clients = [ + { name: 'Client 1', logo: '/assets/img/logos/pgs.jpeg' }, + { name: 'Client 2', logo: '/assets/img/logos/pharmaderm.jpeg' }, + { name: 'Client 3', logo: '/assets/img/logos/sagemcom.jpeg' }, + { name: 'Client 4', logo: '/assets/img/logos/TT.jpeg' }, + { name: 'Client 5', logo: '/assets/img/logos/pharmaservice.jpeg' }, + { name: 'Client 6', logo: '/assets/img/logos/cftp.jpeg' }, + { name: 'Client 7', logo: '/assets/img/logos/cnam.jpeg' }, + { name: 'Client 8', logo: '/assets/img/logos/etap.jpeg' }, + { name: 'Client 9', logo: '/assets/img/logos/smtf.jpeg' }, + { name: 'Client 10', logo: '/assets/img/logos/TLS.jpeg' }, + + + ]; + + duplicatedClients = [...this.clients, ...this.clients]; // Pour l'effet infini + private animationFrameId: number; + private scrollSpeed = 1; // Ajustez la vitesse ici + private carouselElement: HTMLElement; + + ngOnInit(): void { + this.carouselElement = document.querySelector('.carousel-content'); + this.startAnimation(); + } + + ngOnDestroy(): void { + this.stopAnimation(); + } + + startAnimation(): void { + let position = 0; + const animate = () => { + position += this.scrollSpeed; + + // Réinitialiser la position quand on arrive à la moitié (grâce aux éléments dupliqués) + if (position >= this.carouselElement.scrollWidth / 2) { + position = 0; + } + + this.carouselElement.style.transform = `translateX(-${position}px)`; + this.animationFrameId = requestAnimationFrame(animate); + }; + + this.animationFrameId = requestAnimationFrame(animate); + } + + stopAnimation(): void { + cancelAnimationFrame(this.animationFrameId); + } +} \ No newline at end of file diff --git a/src/app/contact-us/contact-us.component.html b/src/app/contact-us/contact-us.component.html new file mode 100644 index 000000000..0c44b12e7 --- /dev/null +++ b/src/app/contact-us/contact-us.component.html @@ -0,0 +1,98 @@ + + + + +
+
+
+
+

{{ 'CONTACT.TITLE' | translate }}

+

{{ 'CONTACT.INTRO' | translate }}

+
+
+ +
+
+ + + + + +
+ {{ 'CONTACT.FORM.EMAIL_ERROR' | translate }} +
+ + + + +
+ +
+ {{ messageText }} +
+
+ + +
+
+
+ + diff --git a/src/app/contact-us/contact-us.component.scss b/src/app/contact-us/contact-us.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/contact-us/contact-us.component.spec.ts b/src/app/contact-us/contact-us.component.spec.ts new file mode 100644 index 000000000..ecc463a0d --- /dev/null +++ b/src/app/contact-us/contact-us.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ContactUsComponent } from './contact-us.component'; + +describe('ContactUsComponent', () => { + let component: ContactUsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ContactUsComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ContactUsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/contact-us/contact-us.component.ts b/src/app/contact-us/contact-us.component.ts new file mode 100644 index 000000000..4226b71a1 --- /dev/null +++ b/src/app/contact-us/contact-us.component.ts @@ -0,0 +1,91 @@ +import { Component, OnInit } from '@angular/core'; +import emailjs from 'emailjs-com'; +import Swal from 'sweetalert2'; + +declare var Email: any; +@Component({ + selector: 'app-contact-us', + templateUrl: './contact-us.component.html', + styleUrls: ['./contact-us.component.scss'] +}) +export class ContactUsComponent implements OnInit { + + constructor() { } + form = { + name: '', + email: '', + message: '' + }; + + ngOnInit(): void { + const script = document.createElement('script'); + script.src = 'https://smtpjs.com/v3/smtp.js'; + script.type = 'text/javascript'; + document.body.appendChild(script); + } + messageBox = document.getElementById('messageBox'); + + showMessage(text, isError = false) { + this.messageBox.textContent = text; + this.messageBox.style.color = isError ? 'red' : 'green'; + this.messageBox.style.display = 'block'; +} +emailInvalid = false; +messageText = ''; +messageError = false; + +isValidEmail(email: string): boolean { + const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return re.test(email); +} + + sendEmail() { + if (!this.isValidEmail(this.form.email)) { + Swal.fire({ + icon: 'error', + title: 'Email invalide', + text: 'Veuillez entrer un email valide.', + }); + return; + } + + const serviceID = 'service_x3dvh7z'; + const template = 'template_al9cbrs'; + const publicKey = 'sDFgBbOfrGRnnhc0V'; + + const templateParams = { + from_name: this.form.name, + from_email: this.form.email, + message: this.form.message, + to_name: 'Tunav Team', + reply_to: this.form.email, + }; + + emailjs.send(serviceID, template, templateParams, publicKey) + .then(() => { + Swal.fire({ + icon: 'success', + title: 'Message envoyé ✅', + html: ` +

+ Un e-mail de confirmation automatique va vous être envoyé immédiatement.

+ ⚠️ Si vous ne le recevez pas dans quelques minutes, cela signifie que vous avez saisi une adresse e-mail inexistante ou incorrecte. + `, + confirmButtonColor: '#3085d6' + }); + + this.form = { name: '', email: '', message: '' }; + }) + .catch((err) => { + Swal.fire({ + icon: 'error', + title: 'Erreur', + text: 'L\'adresse email est incorrecte .', + }); + console.error(err); + }); +} + + + +} diff --git a/src/app/footer-home-page/footer-home-page.component.html b/src/app/footer-home-page/footer-home-page.component.html new file mode 100644 index 000000000..1e239d8b7 --- /dev/null +++ b/src/app/footer-home-page/footer-home-page.component.html @@ -0,0 +1,32 @@ + + + diff --git a/src/app/footer-home-page/footer-home-page.component.scss b/src/app/footer-home-page/footer-home-page.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/footer-home-page/footer-home-page.component.spec.ts b/src/app/footer-home-page/footer-home-page.component.spec.ts new file mode 100644 index 000000000..8eb44750a --- /dev/null +++ b/src/app/footer-home-page/footer-home-page.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FooterHomePageComponent } from './footer-home-page.component'; + +describe('FooterHomePageComponent', () => { + let component: FooterHomePageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ FooterHomePageComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FooterHomePageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/footer-home-page/footer-home-page.component.ts b/src/app/footer-home-page/footer-home-page.component.ts new file mode 100644 index 000000000..df8b1b75f --- /dev/null +++ b/src/app/footer-home-page/footer-home-page.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-footer-home-page', + templateUrl: './footer-home-page.component.html', + styleUrls: ['./footer-home-page.component.scss'] +}) +export class FooterHomePageComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/src/app/form-franchise/form-franchise.component.css b/src/app/form-franchise/form-franchise.component.css new file mode 100644 index 000000000..b2a270f14 --- /dev/null +++ b/src/app/form-franchise/form-franchise.component.css @@ -0,0 +1,142 @@ +.body { + background-image: url('assets/img/infinite-loop-01.jpg'); + background-size: cover; + background-repeat: no-repeat; + background-position: center; +} + +.container { + display: flex; + flex-wrap: wrap; + gap: 40px; + padding: 2rem; + max-width: 1200px; + margin: auto; + color: white; +} + +.partie-informations { + margin-top: 50px; + flex-grow: 1; + flex-shrink: 1; + flex-basis: 40%; +} + +.partie-formulaire { + flex-grow: 1; + flex-shrink: 1; + flex-basis: 60%; +} + +h1 { + text-align: center; + color: #01b4ff; +} + +.subtitle { + text-align: center; + color: #ffffff; + margin-bottom: 1.5rem; +} + +.form-container { + background: white; + padding: 2rem; + border-radius: 12px; + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); + color: rgba(0, 0, 0, 0.766); +} + +.form-container h2 { + font-size: 1.5rem; + margin-bottom: 0.8rem; + color: #01b4ff; + font-weight: 600; +} + +.form-container p { + font-size: 0.9rem; + margin-bottom: 1rem; +} + +.form-container form { + display: flex; + flex-direction: column; + gap: 0.8rem; +} + +.form-container label { + font-size: 0.9rem; +} + +.form-container input:not([type="radio"]), +.form-container textarea { + padding: 0.6rem; + border-radius: 6px; + border: 1px solid #ccc; + font-size: 0.9rem; +} + +textarea { + resize: vertical; + height: 80px; +} + +.form-container button { + margin-top: 1rem; + background-color: #01b4ff; + border: none; + color: white; + padding: 0.75rem; + font-size: 0.95rem; + font-weight: bold; + border-radius: 6px; + cursor: pointer; + transition: background 0.3s ease; +} + +.form-container button:hover { + background: linear-gradient(to right, #1e88e5, #1565c0); +} + +@media (max-width: 768px) { + .container { + flex-direction: column; + padding: 1rem; + } + + .form-container { + padding: 1.2rem; + } +} +input[type="radio"]{ + border: #01b4ff; + gap: 30px; +} + +input::placeholder, +textarea::placeholder { + color:black; +} +.iti { + display: flex; + border: 1px solid #ccc; + border-radius: 6px; + overflow: hidden; +} + +.iti__flag-container { + background-color: #f3f3f3; + border-right: 1px solid #ccc; + padding: 0 10px; + display: flex; + align-items: center; +} + +input.form-control { + border: none; + flex: 1; + padding: 10px; + font-size: 1rem; +} + diff --git a/src/app/form-franchise/form-franchise.component.html b/src/app/form-franchise/form-franchise.component.html new file mode 100644 index 000000000..c6e3c8b91 --- /dev/null +++ b/src/app/form-franchise/form-franchise.component.html @@ -0,0 +1,80 @@ +
+ + +
+
+
+

{{ 'FRANCHISE.TITLE' | translate }}

+

+ {{ 'FRANCHISE.SUBTITLE' | translate }} +

+
+ +
+
+
+

{{ 'FRANCHISE.FORM.TITLE' | translate }}

+

{{ 'FRANCHISE.FORM.DESCRIPTION' | translate }}

+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + +
+ + +
+ + +
+ +
+ + +
+ + + + + +
+
+
+
+


+ +
diff --git a/src/app/form-franchise/form-franchise.component.spec.ts b/src/app/form-franchise/form-franchise.component.spec.ts new file mode 100644 index 000000000..60243b7bd --- /dev/null +++ b/src/app/form-franchise/form-franchise.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FormFranchiseComponent } from './form-franchise.component'; + +describe('FormFranchiseComponent', () => { + let component: FormFranchiseComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ FormFranchiseComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FormFranchiseComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/form-franchise/form-franchise.component.ts b/src/app/form-franchise/form-franchise.component.ts new file mode 100644 index 000000000..27371e002 --- /dev/null +++ b/src/app/form-franchise/form-franchise.component.ts @@ -0,0 +1,141 @@ +import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { FranchiseService } from 'app/Services/franchise.service'; +import Swal from 'sweetalert2'; + +declare var intlTelInput: any; + +@Component({ + selector: 'app-form-franchise', + templateUrl: './form-franchise.component.html', + styleUrls: ['./form-franchise.component.css'] +}) +export class FormFranchiseComponent implements OnInit, AfterViewInit { + @ViewChild('phoneInput', { static: false }) phoneInputRef!: ElementRef; + formFranchise!: FormGroup; + iti: any; + + constructor(private fb: FormBuilder,private franchiseService: FranchiseService) {} + + ngOnInit(): void { + this.formFranchise = this.fb.group({ + nom: ['', Validators.required], + prenom: ['', Validators.required], + telephone: ['', Validators.required], + email: ['', [Validators.required, Validators.email]], + profession: [''], + experienceIT: ['', Validators.required], + precisionsExp: [''], + dirigeEntreprise: ['', Validators.required], + secteurDuree: [''], + motivation: ['', Validators.required], + }); + } + + ngAfterViewInit(): void { + this.iti = intlTelInput(this.phoneInputRef.nativeElement, { + initialCountry: 'tn', + utilsScript:'https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js' + }); + } + +onSubmit(): void { + const localNumber = this.phoneInputRef.nativeElement.value.trim(); + const countryData = this.iti.getSelectedCountryData(); + const dialCode = countryData?.dialCode || ''; + + if (!localNumber || !dialCode) { + Swal.fire('Erreur', 'Numéro ou pays non sélectionné.', 'error'); + return; + } + + const phoneRegex = /^[0-9]{6,15}$/; + + if (!phoneRegex.test(localNumber)) { + Swal.fire('Numéro invalide', 'Le numéro ne doit contenir que des chiffres (pas de lettres ni de symboles).', 'error'); + return; + } + + const internationalNumber = `+${dialCode}${localNumber.replace(/^0+/, '')}`; + this.formFranchise.patchValue({ telephone: internationalNumber }); + + // 🔐 Validation conditionnelle + if (this.formFranchise.get('experienceIT')?.value === 'oui' && + !this.formFranchise.get('precisionsExp')?.value.trim()) { + Swal.fire('Champ requis', 'Veuillez préciser votre expérience en IT/IoT/GPS.', 'warning'); + return; + } + + if (this.formFranchise.get('dirigeEntreprise')?.value === 'oui' && + !this.formFranchise.get('secteurDuree')?.value.trim()) { + Swal.fire('Champ requis', 'Veuillez préciser le secteur et la durée de l’entreprise que vous avez dirigée.', 'warning'); + return; + } + + // 🔍 Validation générale + if (!this.formFranchise.valid) { + const controls = this.formFranchise.controls; + + if (controls['nom'].invalid) { + Swal.fire('Champ requis', 'Veuillez renseigner votre nom.', 'warning'); + return; + } + + if (controls['prenom'].invalid) { + Swal.fire('Champ requis', 'Veuillez renseigner votre prénom.', 'warning'); + return; + } + + if (controls['email'].invalid) { + Swal.fire('Email invalide', 'Veuillez saisir une adresse email valide.', 'error'); + return; + } + + if (controls['experienceIT'].invalid) { + Swal.fire('Champ requis', 'Veuillez indiquer votre expérience.', 'warning'); + return; + } + + if (controls['dirigeEntreprise'].invalid) { + Swal.fire('Champ requis', 'Veuillez préciser si vous avez dirigé une entreprise.', 'warning'); + return; + } + + if (controls['motivation'].invalid) { + Swal.fire('Champ requis', 'Veuillez indiquer vos motivations.', 'warning'); + return; + } + + this.formFranchise.markAllAsTouched(); + return; + } + + const formData = this.formFranchise.value; + +const payload = { + nom: formData.nom, + prenom: formData.prenom, + email: formData.email, + telephone: internationalNumber, + professionActuelle: formData.profession || '', + experienceIotGps: formData.experienceIT === 'oui' ? formData.precisionsExp : '', + entrepriseDirige: formData.dirigeEntreprise === 'oui' ? formData.secteurDuree : '', + motivation: formData.motivation, + userId: 1 +}; + +this.franchiseService.envoyerDemandeFranchise(payload).subscribe({ + next: (res) => { + console.log('Réponse API :', res); + Swal.fire('Succès', 'Votre demande a bien été envoyée.', 'success'); + this.formFranchise.reset(); + }, + error: (err) => { + console.error('Erreur API :', err); + Swal.fire('Erreur', 'Une erreur est survenue lors de l\'envoi.', 'error'); + } +}); + +} + +} diff --git a/src/app/form-products/form-products.component.html b/src/app/form-products/form-products.component.html new file mode 100644 index 000000000..3f1a0a64d --- /dev/null +++ b/src/app/form-products/form-products.component.html @@ -0,0 +1,39 @@ +
+

{{ isEditMode ? 'Modifier un Produit' : 'Ajouter un Produit ' + (type === 'iot' ? 'IoT' : 'GPS') }}

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+

Glissez-déposez une image ici ou cliquez

+ Image + +
+ +
+ + +
+
diff --git a/src/app/form-products/form-products.component.scss b/src/app/form-products/form-products.component.scss new file mode 100644 index 000000000..ee11ce7fb --- /dev/null +++ b/src/app/form-products/form-products.component.scss @@ -0,0 +1,11 @@ +.drag-drop-area { + border: 2px dashed #aaa; + padding: 20px; + text-align: center; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.drag-drop-area:hover { + background-color: #f1f1f1; +} diff --git a/src/app/form-products/form-products.component.spec.ts b/src/app/form-products/form-products.component.spec.ts new file mode 100644 index 000000000..250095eaa --- /dev/null +++ b/src/app/form-products/form-products.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FormProductsComponent } from './form-products.component'; + +describe('FormProductsComponent', () => { + let component: FormProductsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ FormProductsComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FormProductsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/form-products/form-products.component.ts b/src/app/form-products/form-products.component.ts new file mode 100644 index 000000000..bcde1741a --- /dev/null +++ b/src/app/form-products/form-products.component.ts @@ -0,0 +1,115 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ReactiveFormsModule } from '@angular/forms'; +@Component({ + selector: 'app-form-products', + templateUrl: './form-products.component.html', + styleUrls: ['./form-products.component.scss'] +}) +export class FormProductsComponent implements OnInit { + + productForm!: FormGroup; + isEditMode = false; + productId: number | null = null; + previewUrl: string | ArrayBuffer | null = null; + selectedFile: File | null = null; + + type: 'iot' | 'gps' = 'gps'; + + constructor( + private fb: FormBuilder, + private route: ActivatedRoute, + private router: Router + ) {} + + ngOnInit(): void { + this.productForm = this.fb.group({ + nom: [''], + description: [''], + prix: this.type === 'gps' ? [''] : null, + image: [null] + }); + + this.productId = Number(this.route.snapshot.paramMap.get('id')); + if (this.productId) { + this.isEditMode = true; + const product = this.getProductById(this.productId); + if (product) { + this.productForm.patchValue({ + nom: product.nom, + description: product.description, + prix: product.prix + }); + this.previewUrl = product.image; + } + } + const routeType = this.route.snapshot.queryParamMap.get('type'); + if (routeType === 'iot' || routeType === 'gps') { + this.type = routeType; + } + } + + onSubmit(): void { + const formValue = this.productForm.value; + if (this.type === 'iot') { + console.log('Produit IoT envoyé :', { + titre: formValue.nom, + description: formValue.description, + image: this.selectedFile, + categorie: 'iot' + }); + } else { + console.log('Produit GPS envoyé :', { + titre: formValue.nom, + description: formValue.description, + prix: formValue.prix, + image: this.selectedFile, + categorie: 'gps' + }); + } + this.router.navigate(['/listProducts']); + } + + onDragOver(event: DragEvent) { + event.preventDefault(); + } + + onDragLeave(event: DragEvent) { + event.preventDefault(); + } + + onFileDrop(event: DragEvent) { + event.preventDefault(); + if (event.dataTransfer?.files.length) { + this.handleFile(event.dataTransfer.files[0]); + } + } + + onFileSelect(event: any) { + if (event.target.files.length) { + this.handleFile(event.target.files[0]); + } + } + + handleFile(file: File) { + this.selectedFile = file; + this.productForm.patchValue({ image: file }); + const reader = new FileReader(); + reader.onload = () => { + this.previewUrl = reader.result; + }; + reader.readAsDataURL(file); + } + + getProductById(id: number) { + return { + id, + nom: 'Produit Exemple', + description: 'Description exemple', + prix: 99.99, + image: 'https://via.placeholder.com/150' + }; + } + +} diff --git a/src/app/formblog/formblog.component.html b/src/app/formblog/formblog.component.html new file mode 100644 index 000000000..11a6e2414 --- /dev/null +++ b/src/app/formblog/formblog.component.html @@ -0,0 +1,53 @@ +
+
+

{{ isEditMode ? 'Modifier le blog' : 'Ajouter un blog' }}

+
+ +
+ +
+ + +
+ + +
+ + +
+ + +
+ +
+ + {{ tag}} + + +
+ +
+ + +
+ +
+

Glissez-déposez une image ici ou sélectionnez un fichier.

+ Aperçu de l'image + +
+
+ + + +
+
diff --git a/src/app/formblog/formblog.component.scss b/src/app/formblog/formblog.component.scss new file mode 100644 index 000000000..f5272583a --- /dev/null +++ b/src/app/formblog/formblog.component.scss @@ -0,0 +1,20 @@ +.image-upload-container { + border: 2px dashed #3377AA; + padding: 20px; + text-align: center; + border-radius: 10px; + background-color: #f8f9fa; + transition: background-color 0.2s ease-in-out; + + &.drag-over { + background-color: #e0f3ff; + } + + .image-preview { + max-width: 200px; + max-height: 200px; + margin-top: 10px; + border-radius: 10px; + object-fit: cover; + } +} diff --git a/src/app/formblog/formblog.component.spec.ts b/src/app/formblog/formblog.component.spec.ts new file mode 100644 index 000000000..edfef247f --- /dev/null +++ b/src/app/formblog/formblog.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FormblogComponent } from './formblog.component'; + +describe('FormblogComponent', () => { + let component: FormblogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ FormblogComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FormblogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/formblog/formblog.component.ts b/src/app/formblog/formblog.component.ts new file mode 100644 index 000000000..853b661d2 --- /dev/null +++ b/src/app/formblog/formblog.component.ts @@ -0,0 +1,149 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { BlogCreateRequest, BlogService, BlogUpdateRequest } from 'app/Services/BlogService'; +import { Blog } from 'app/blogslist/blogslist.component'; +import { environment } from 'environments/environment'; + +@Component({ + selector: 'app-formblog', + templateUrl: './formblog.component.html', + styleUrls: ['./formblog.component.scss'] +}) +export class FormblogComponent implements OnInit { + + blog: Blog = { + id: 0, + titre: '', + contenu: '', + imagePath: '', + tags: [], + likes: 0 + }; + + selectedFile?: File; + newTag: string = ''; + isEditMode = false; + isDragging = false; + apiBaseUrl = environment.baseUrl; + + constructor( + private route: ActivatedRoute, + private router: Router, + private blogService: BlogService + ) {} + + ngOnInit() { + const blogId = this.route.snapshot.paramMap.get('id'); + console.log("le is est",blogId); + if (blogId) { + this.isEditMode = true; + this.blogService.getBlogById(+blogId).subscribe({ + next: data => { + this.blog = {...data, + tags: data.tags.map((t: any) => t.nom || t) + + } + console.log("le blog filtré",this.blog); + }, + error: err => console.error('Erreur chargement blog :', err) + }); + } + } + + onFileSelected(event: any) { + this.selectedFile = event.target.files[0]; + if (this.selectedFile && this.selectedFile.type.startsWith('image/')) { + const reader = new FileReader(); + reader.onload = e => this.blog.imagePath = (e.target as FileReader).result as string; + reader.readAsDataURL(this.selectedFile); + } + } + + onDragOver(event: DragEvent) { + event.preventDefault(); + this.isDragging = true; + } + + onDragLeave(event: DragEvent) { + event.preventDefault(); + this.isDragging = false; + } + + onDrop(event: DragEvent) { + event.preventDefault(); + this.isDragging = false; + const file = event.dataTransfer?.files[0]; + if (file && file.type.startsWith('image/')) { + this.selectedFile = file; + const reader = new FileReader(); + reader.onload = () => this.blog.imagePath = reader.result as string; + reader.readAsDataURL(file); + } + } + + addTag(event: KeyboardEvent) { + event.preventDefault(); + const tag = this.newTag.trim(); + const tagExists = this.blog.tags.includes(tag); + if (tag && !tagExists) { + this.blog.tags.push(tag); + this.newTag = ''; + } + } + + removeTag(index: number) { + this.blog.tags.splice(index, 1); + } + + onSubmit() { + const CreateRequest: BlogCreateRequest = { + titre: this.blog.titre, + contenu: this.blog.contenu, + userId: 1, // à remplacer par l'utilisateur connecté + tags: this.blog.tags, // s'assurer que ce sont bien des strings + image: this.selectedFile || undefined + }; + const UpdateRequest: BlogUpdateRequest = { + titre: this.blog.titre, + contenu: this.blog.contenu, + tags: this.blog.tags, + newImage: this.selectedFile || undefined + }; + + if (this.isEditMode) { + console.log(UpdateRequest); + this.blogService.updateBlog(this.blog.id, UpdateRequest).subscribe({ + next: () => this.router.navigate(['/listblogs']), + error: (err) => { + console.error('Erreur update blog:', err); + if (err.error instanceof Blob) { + const reader = new FileReader(); + reader.onload = () => console.error('Erreur backend JSON:', reader.result); + reader.readAsText(err.error); + } else { + console.error('Erreur directe:', err.error); + } + } + }); + } else { + this.blogService.createBlog(CreateRequest).subscribe({ + next: () => this.router.navigate(['/listblogs']), + error: (err) => { + console.error('Erreur création blog:', err); + if (err.error instanceof Blob) { + const reader = new FileReader(); + reader.onload = () => console.error('Erreur backend:', reader.result); + reader.readAsText(err.error); + } else { + console.error('Erreur JSON:', err.error); + } + } + }); + } + } + + getImageUrl(imagePath: string): string { + return this.apiBaseUrl + imagePath; + } + +} diff --git a/src/app/formulaire-iot-it/formulaire-iot-it.component.css b/src/app/formulaire-iot-it/formulaire-iot-it.component.css new file mode 100644 index 000000000..102e441d7 --- /dev/null +++ b/src/app/formulaire-iot-it/formulaire-iot-it.component.css @@ -0,0 +1,129 @@ +.body{ + background-image: url('assets/img/infinite-loop-01.jpg'); +} +.container { + /* margin-top: 20px; */ + display: flex; + /* padding: 2rem; */ + min-width: 100%; + background: transparent; + font-family: 'Segoe UI', sans-serif; + box-shadow: 0 5px 12px rgba(0, 0, 0, 0.1); + gap: 30px; + justify-content: center; + align-items: center; + height: 695px; +} + +.partie-informations { + width: auto; +} + +.partie-formulaire { + width: 65%; + margin-right: 30px; +} +h1 { + text-align: center; + color: #01b4ff; +} + +.subtitle { + text-align: center; + color: #ffffff; + margin-bottom: 1.5rem; +} +.tabs { + display: flex; + justify-content: center; + margin-bottom: 2rem; + gap: 1rem; +} +.tabs .tab { + padding: 0.6rem 1.5rem; + border-radius: 30px; + border: none; + background: #e0e0e0; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 500; +} + +.tabs .tab.active { + background-color:#01b4ff; + color: white; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); +} + +.form-container { + margin-top: 20px; + margin-bottom: 20px; + background: white; + padding: 1rem; + border-radius: 12px; + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); + height: 600px; + max-height: 100%; + margin: auto 0; + display: flex; + flex-direction: column; + justify-content: space-between; + overflow: hidden; +} + +.form-container .description { + font-size: 0.85rem; + margin-bottom: 0.6rem; +} + +.form-container h2 { + font-size: 1.3rem; + margin-bottom: 0.3rem; +} + +.form-container strong { + font-size: 0.9rem; +} + +.form-container form { + display: flex; + flex-direction: column; + flex-grow: 1; + justify-content: space-evenly; +} + +.form-container form label { + font-size: 0.85rem; + margin-bottom: 0.2rem; +} + +.form-container form input, +.form-container form textarea { + margin: 0.3rem 0; + padding: 0.6rem; + border-radius: 6px; + border: 1px solid #ccc; + font-size: 0.85rem; +} + +.form-container form textarea { + resize: vertical; + height: 70px; +} + +.form-container form button { + margin-top: 0.6rem; + background-color: #01b4ff; + border: none; + color: white; + padding: 0.7rem; + font-size: 0.85rem; + font-weight: bold; + border-radius: 6px; + cursor: pointer; + transition: background 0.3s; +} + +.form-container form button:hover { + background: linear-gradient(to right, #1e88e5, #1565c0); +} diff --git a/src/app/formulaire-iot-it/formulaire-iot-it.component.html b/src/app/formulaire-iot-it/formulaire-iot-it.component.html new file mode 100644 index 000000000..bc6c2ae92 --- /dev/null +++ b/src/app/formulaire-iot-it/formulaire-iot-it.component.html @@ -0,0 +1,81 @@ +
+ +
+
+
+

{{ 'SOLUTIONS.TITLE' | translate }}

+

{{ 'SOLUTIONS.SUBTITLE' | translate }}

+ +
+ + +
+
+ +
+ +
+

{{ 'SOLUTIONS.IOT.TITLE' | translate }}

+

+ {{ 'SOLUTIONS.IOT.SUBTITLE' | translate }}
+ {{ 'SOLUTIONS.IOT.DESCRIPTION' | translate }} +

+ +
+ + + + + + + + + + + + + + + + +
+
+ + +
+

{{ 'SOLUTIONS.IT.TITLE' | translate }}

+

+ {{ 'SOLUTIONS.IT.SUBTITLE' | translate }}
+ {{ 'SOLUTIONS.IT.DESCRIPTION' | translate }} +

+ +
+ + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+ +
diff --git a/src/app/formulaire-iot-it/formulaire-iot-it.component.spec.ts b/src/app/formulaire-iot-it/formulaire-iot-it.component.spec.ts new file mode 100644 index 000000000..1fb027ef9 --- /dev/null +++ b/src/app/formulaire-iot-it/formulaire-iot-it.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FormulaireIotItComponent } from './formulaire-iot-it.component'; + +describe('FormulaireIotItComponent', () => { + let component: FormulaireIotItComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ FormulaireIotItComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FormulaireIotItComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/formulaire-iot-it/formulaire-iot-it.component.ts b/src/app/formulaire-iot-it/formulaire-iot-it.component.ts new file mode 100644 index 000000000..13940a6a9 --- /dev/null +++ b/src/app/formulaire-iot-it/formulaire-iot-it.component.ts @@ -0,0 +1,48 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; + +@Component({ + selector: 'app-formulaire-iot-it', + templateUrl: './formulaire-iot-it.component.html', + styleUrls: ['./formulaire-iot-it.component.css'] +}) +export class FormulaireIotItComponent implements OnInit { + selectedTab: 'iot' | 'it' = 'iot'; + formIot!: FormGroup; + formIt!: FormGroup; + + constructor(private fb: FormBuilder) {} + + ngOnInit(): void { + this.formIot = this.fb.group({ + nom: ['', Validators.required], + prenom: ['', Validators.required], + email: ['', [Validators.required, Validators.email]], + entreprise: [''], + message: ['', Validators.required] + }); + + this.formIt = this.fb.group({ + nom: ['', Validators.required], + prenom: ['', Validators.required], + email: ['', [Validators.required, Validators.email]], + entreprise: [''], + message: ['', Validators.required] + }); + } + + selectTab(tab: 'iot' | 'it') { + this.selectedTab = tab; + } + + submitForm(type: 'iot' | 'it') { + const form = type === 'iot' ? this.formIot : this.formIt; + if (form.valid) { + console.log(`Formulaire ${type.toUpperCase()} :`, form.value); + alert(`Formulaire ${type.toUpperCase()} soumis avec succès !`); + form.reset(); + } else { + form.markAllAsTouched(); + } + } +} diff --git a/src/app/franchise-detail/franchise-detail.component.html b/src/app/franchise-detail/franchise-detail.component.html new file mode 100644 index 000000000..70106a79e --- /dev/null +++ b/src/app/franchise-detail/franchise-detail.component.html @@ -0,0 +1,52 @@ +
+
+
+
+

📋 Détails de la demande de franchise

+
+ +
+
+
+
👤 Informations personnelles
+

Nom : {{ franchise.nom }}

+

Prénom : {{ franchise.prenom }}

+

Email : {{ franchise.email }}

+

Téléphone : {{ franchise.telephone }}

+
+
+
💼 Expérience & Motivation
+

Profession actuelle : {{ franchise.professionActuelle }}

+

Expérience IoT/GPS : + + {{ franchise.experienceIotGps || 'Non' }} + +

+

A dirigé une entreprise : + + {{ franchise.entrepriseDirige || 'Non' }} + +

+

Motivation :
+ {{ franchise.motivation }} +

+
+
+ +
+ + ⬅ Retour à la liste + + + + + +
+
+
+
+
diff --git a/src/app/franchise-detail/franchise-detail.component.scss b/src/app/franchise-detail/franchise-detail.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/franchise-detail/franchise-detail.component.spec.ts b/src/app/franchise-detail/franchise-detail.component.spec.ts new file mode 100644 index 000000000..ef9f93885 --- /dev/null +++ b/src/app/franchise-detail/franchise-detail.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FranchiseDetailComponent } from './franchise-detail.component'; + +describe('FranchiseDetailComponent', () => { + let component: FranchiseDetailComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ FranchiseDetailComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FranchiseDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/franchise-detail/franchise-detail.component.ts b/src/app/franchise-detail/franchise-detail.component.ts new file mode 100644 index 000000000..d53777dab --- /dev/null +++ b/src/app/franchise-detail/franchise-detail.component.ts @@ -0,0 +1,70 @@ +import { Component, ElementRef, OnInit,ViewChild } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Franchise } from 'app/liste-franchises/liste-franchises.component'; +import { FranchiseService } from 'app/Services/franchise.service'; +import * as html2pdf from 'html2pdf.js'; + +@Component({ + selector: 'app-franchise-detail', + templateUrl: './franchise-detail.component.html', + styleUrls: ['./franchise-detail.component.scss'] +}) +export class FranchiseDetailComponent implements OnInit { + + franchise?: Franchise; + @ViewChild('pdfContent') pdfContent!: ElementRef; + constructor( + private route: ActivatedRoute, + private franchiseService: FranchiseService + ) {} + + ngOnInit(): void { + const id = Number(this.route.snapshot.paramMap.get('id')); + this.franchiseService.getFranchiseById(id).subscribe(data => { + this.franchise = data; + }); + } + + envoyerMailConfirmation(): void { + alert(`Un email de confirmation a été envoyé à ${this.franchise?.email}`); + } + + genererPdf() { + const logoUrl = '/assets/img/logoTunav.png'; + const franchise = this.franchise; + + const content = ` +
+
+ Logo +

Fiche Franchise - TUNAV

+
+
+

Nom : ${franchise?.nom || ''}

+

Prénom : ${franchise?.prenom || ''}

+

Email : ${franchise?.email || ''}

+

Téléphone : ${franchise?.telephone || ''}

+

Profession actuelle : ${franchise?.professionActuelle || 'Non spécifiée'}

+

Expérience IT/IoT/GPS : ${franchise?.experienceIotGps || 'Non spécifiée'}

+

Entreprise dirigée : ${franchise?.entrepriseDirige || 'Non spécifiée'}

+

Motivation :
${franchise?.motivation || ''}

+
+
+ `; + + const opt = { + margin: 0.5, + filename: `Fiche_Franchise_${franchise?.nom}_${franchise?.prenom}.pdf`, + image: { type: 'jpeg', quality: 0.98 }, + html2canvas: { scale: 2 }, + jsPDF: { unit: 'in', format: 'a4', orientation: 'portrait' } + }; + + const element = document.createElement('div'); + element.innerHTML = content; + +html2pdf().set(opt).from(element).save(); +} + + +} diff --git a/src/app/header-home-page/header-home-page.component.html b/src/app/header-home-page/header-home-page.component.html new file mode 100644 index 000000000..a7a6f7a06 --- /dev/null +++ b/src/app/header-home-page/header-home-page.component.html @@ -0,0 +1,43 @@ + diff --git a/src/app/header-home-page/header-home-page.component.scss b/src/app/header-home-page/header-home-page.component.scss new file mode 100644 index 000000000..fc513019d --- /dev/null +++ b/src/app/header-home-page/header-home-page.component.scss @@ -0,0 +1,41 @@ +.language-switcher { + position: relative; + z-index: 2; + + .current-language { + font-size: 1.5rem; + background: none; + border: none; + cursor: pointer; + } + + .language-menu { + display: none; + position: absolute; + top: 2rem; + left: 0; + list-style: none; + background: #ffffff; + border: 1px solid #ddd; + padding: 0.5rem 0; + margin: 0; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + + li { + padding: 0.5rem 1rem; + cursor: pointer; + + &:hover { + background-color: #f5f5f5; + } + + &.active { + font-weight: bold; + } + } + } + + &:hover .language-menu { + display: block; + } +} \ No newline at end of file diff --git a/src/app/header-home-page/header-home-page.component.spec.ts b/src/app/header-home-page/header-home-page.component.spec.ts new file mode 100644 index 000000000..67de4addc --- /dev/null +++ b/src/app/header-home-page/header-home-page.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HeaderHomePageComponent } from './header-home-page.component'; + +describe('HeaderHomePageComponent', () => { + let component: HeaderHomePageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ HeaderHomePageComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(HeaderHomePageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/header-home-page/header-home-page.component.ts b/src/app/header-home-page/header-home-page.component.ts new file mode 100644 index 000000000..c3a8a89ee --- /dev/null +++ b/src/app/header-home-page/header-home-page.component.ts @@ -0,0 +1,99 @@ +import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { NavigationEnd, Router } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { LanguageService } from 'app/Services/language.service'; + +@Component({ + selector: 'app-header-home-page', + templateUrl: './header-home-page.component.html', + styleUrls: ['./header-home-page.component.scss'] +}) +export class HeaderHomePageComponent implements OnInit { + isDropdownOpen = true; + pendingSection: string | null = null; + languages = [ + { code: 'en', name: 'English', flag: '/assets/img/flags/united-kingdom-flag.png' }, + { code: 'fr', name: 'Francais', flag: '/assets/img/flags/france-flag.png' } +]; + + currentLanguage = 'en'; + constructor(private router: Router,private translate: TranslateService,private languageService: LanguageService,private cdr: ChangeDetectorRef) { + this.currentLanguage = this.languageService.getCurrentLanguage(); + console.log(this.isDropdownOpen); + this.router.events.subscribe(event => { + if (event instanceof NavigationEnd && this.pendingSection) { + setTimeout(() => { + const el = document.getElementById(this.pendingSection!); + if (el) { + el.scrollIntoView({ behavior: 'smooth' }); + } + this.pendingSection = null; + }, 100); // délai pour que la page ait le temps de s'afficher + } + }); + } + + + switchLanguage(languageCode: string): void { + this.currentLanguage = languageCode; + this.translate.use(languageCode); + localStorage.setItem('language', languageCode); + this.isDropdownOpen = false; + } + changeDropDown() + { + this.isDropdownOpen=!this.isDropdownOpen; + this.cdr.detectChanges(); + console.log(this.isDropdownOpen); + } + getFlag(languageCode: string): string { + const language = this.languages.find((lang) => lang.code === languageCode); + return language ? language.flag : ''; + } + navigateToSection(sectionId: string, event: Event) { + event.preventDefault(); + + const currentUrl = this.router.url.split('#')[0]; + if (currentUrl !== '/' && currentUrl !== '/home') { + this.pendingSection = sectionId; + this.router.navigate(['/']); + } else { + // Déjà sur la home => scroll direct + const el = document.getElementById(sectionId); + if (el) { + el.scrollIntoView({ behavior: 'smooth' }); + } + } + } + ngOnInit(): void { + window.addEventListener('scroll', this.onScroll); +} + +onScroll = () => { + const scrollY = window.scrollY; + const logoBlanc = document.getElementById('logoBlanc'); + const logoCouleur = document.getElementById('logoCouleur'); + + if (scrollY <= 50) { + logoBlanc!.style.display = 'block'; + logoCouleur!.style.display = 'none'; + } else { + logoBlanc!.style.display = 'none'; + logoCouleur!.style.display = 'block'; + } +}; + + + Login(event: Event){ + event.preventDefault(); + this.router.navigate(['auth']); + } + About(event: Event){ + event.preventDefault(); + this.router.navigate(['about']); + } + Blogs(event: Event){ + event.preventDefault(); + this.router.navigate(['blogs']); + } +} diff --git a/src/app/iotcarousel/iotcarousel.component.html b/src/app/iotcarousel/iotcarousel.component.html new file mode 100644 index 000000000..42191aa88 --- /dev/null +++ b/src/app/iotcarousel/iotcarousel.component.html @@ -0,0 +1,26 @@ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/src/app/iotcarousel/iotcarousel.component.scss b/src/app/iotcarousel/iotcarousel.component.scss new file mode 100644 index 000000000..b4592278a --- /dev/null +++ b/src/app/iotcarousel/iotcarousel.component.scss @@ -0,0 +1,89 @@ +$poussin: url("http://images.metmuseum.org/CRDImages/ep/original/46_160.jpg"); +$picasso: url("http://uploads6.wikiart.org/images/pablo-picasso/the-abduction-of-sabines-1962-1.jpg"); +$rubens: url("https://upload.wikimedia.org/wikipedia/commons/7/72/Peter_Paul_Rubens_(taller)_-_Rapto_de_las_Sabinas.jpg"); + +html, body { + margin: 0; + height: 100%; + background: #F4F1E9; +} + +.arrow { + font-size: 2em; + color: #363f85; + position: absolute; + top: 50%; + left: 50%; + transition: 0.2s; + + &.left-arrow { + transform: translate(-11em, -50%); + } + + &.right-arrow { + transform: translate(10em, -50%); + } + + &:hover { + color: #B3B2AD; + } +} + +.slider { + position: relative; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: 600px; + height: 200px; +} + +.slide { + float: left; + position: relative; + width: 33.3333%; + height: 100%; + overflow-x: hidden; + border-radius: 50%; + box-shadow: 0px 0px 15px rgba(0,0,0,0.5); + + &.slide-center { + z-index: 1; + box-shadow: 0px 0px 15px rgba(0,0,0,0.75); + transform: scale(1.3); + } +} + +.slide-holder { + width: 300%; + height: 100%; + position: relative; + top: 0; + transform: translateX(-33.3333%); + display: flex; +} + +.slide-bg { + width: 33.3333%; + height: 100%; + background-size: cover; + background-position: center center; + background-repeat: no-repeat; + display: inline-block; +} + +#slide-left { + .bg-previous { background-image: $rubens; } + .bg-current { background-image: $poussin; } + .bg-next { background-image: $picasso; } +} +#slide-center { + .bg-previous { background-image: $poussin; } + .bg-current { background-image: $picasso; } + .bg-next { background-image: $rubens; } +} +#slide-right { + .bg-previous { background-image: $picasso; } + .bg-current { background-image: $rubens; } + .bg-next { background-image: $poussin; } +} diff --git a/src/app/iotcarousel/iotcarousel.component.spec.ts b/src/app/iotcarousel/iotcarousel.component.spec.ts new file mode 100644 index 000000000..cd2555c1e --- /dev/null +++ b/src/app/iotcarousel/iotcarousel.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { IOTCarouselComponent } from './iotcarousel.component'; + +describe('IOTCarouselComponent', () => { + let component: IOTCarouselComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ IOTCarouselComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(IOTCarouselComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/iotcarousel/iotcarousel.component.ts b/src/app/iotcarousel/iotcarousel.component.ts new file mode 100644 index 000000000..9d542fe75 --- /dev/null +++ b/src/app/iotcarousel/iotcarousel.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-iotcarousel', + templateUrl: './iotcarousel.component.html', + styleUrls: ['./iotcarousel.component.scss'] +}) +export class IOTCarouselComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/src/app/layouts/admin-layout/admin-layout.routing.ts b/src/app/layouts/admin-layout/admin-layout.routing.ts index e09417d82..54555076b 100644 --- a/src/app/layouts/admin-layout/admin-layout.routing.ts +++ b/src/app/layouts/admin-layout/admin-layout.routing.ts @@ -5,8 +5,6 @@ import { UserComponent } from '../../user/user.component'; import { TablesComponent } from '../../tables/tables.component'; import { TypographyComponent } from '../../typography/typography.component'; import { IconsComponent } from '../../icons/icons.component'; -import { MapsComponent } from '../../maps/maps.component'; -import { NotificationsComponent } from '../../notifications/notifications.component'; import { UpgradeComponent } from '../../upgrade/upgrade.component'; export const AdminLayoutRoutes: Routes = [ @@ -15,7 +13,5 @@ export const AdminLayoutRoutes: Routes = [ { path: 'table', component: TablesComponent }, { path: 'typography', component: TypographyComponent }, { path: 'icons', component: IconsComponent }, - { path: 'maps', component: MapsComponent }, - { path: 'notifications', component: NotificationsComponent }, { path: 'upgrade', component: UpgradeComponent }, ]; diff --git a/src/app/liste-franchises/liste-franchises.component.html b/src/app/liste-franchises/liste-franchises.component.html new file mode 100644 index 000000000..6d483950f --- /dev/null +++ b/src/app/liste-franchises/liste-franchises.component.html @@ -0,0 +1,37 @@ +
+
+

📋 Liste des demandes de franchises

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
NomPrénomEmailTéléphoneProfessionExp. IT/IoT/GPSDéjà dirigé ?Action
{{ f.nom }}{{ f.prenom }}{{ f.email }}{{ f.telephone }}{{ f.professionActuelle }}{{ f.experienceIotGps ? 'Oui' : 'Non' }}{{ f.entrepriseDirige ? 'Oui' : 'Non' }} + + 🔍 Détails + +
+
+
diff --git a/src/app/liste-franchises/liste-franchises.component.scss b/src/app/liste-franchises/liste-franchises.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/liste-franchises/liste-franchises.component.spec.ts b/src/app/liste-franchises/liste-franchises.component.spec.ts new file mode 100644 index 000000000..ee9bc848f --- /dev/null +++ b/src/app/liste-franchises/liste-franchises.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ListeFranchisesComponent } from './liste-franchises.component'; + +describe('ListeFranchisesComponent', () => { + let component: ListeFranchisesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ListeFranchisesComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ListeFranchisesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/liste-franchises/liste-franchises.component.ts b/src/app/liste-franchises/liste-franchises.component.ts new file mode 100644 index 000000000..33a03f665 --- /dev/null +++ b/src/app/liste-franchises/liste-franchises.component.ts @@ -0,0 +1,38 @@ +import { Component, OnInit } from '@angular/core'; +import { FranchiseService } from 'app/Services/franchise.service'; +export interface Franchise { + id: number; + nom: string; + prenom: string; + email: string; + telephone: string; + professionActuelle: string; + experienceIotGps: string; + entrepriseDirige: string; + motivation: string; +} + +@Component({ + selector: 'app-liste-franchises', + templateUrl: './liste-franchises.component.html', + styleUrls: ['./liste-franchises.component.scss'] +}) + +export class ListeFranchisesComponent implements OnInit { + + franchises: Franchise[] = []; + + constructor(private franchiseService: FranchiseService) {} + + ngOnInit(): void { + this.loadFranchises(); + } + + loadFranchises(): void { + this.franchiseService.getFranchises().subscribe({ + next: (data) => this.franchises = data, + error: (err) => console.error('Erreur lors du chargement des franchises', err) + }); + } + +} diff --git a/src/app/navbar/navbar.component.css b/src/app/navbar/navbar.component.css new file mode 100644 index 000000000..250ad7968 --- /dev/null +++ b/src/app/navbar/navbar.component.css @@ -0,0 +1,63 @@ + nav { + position: fixed; + width: 100%; + top: 0; + left: 0; + transition: background-color 0.3s ease, transform 0.5s ease; + z-index: 2; + height: 80px; + display: flex; + align-items: center; +} + +.navbar-container { + width: 100%; + max-width: 1300px; + margin: 0 auto; + padding: 0 30px; + display: flex; + justify-content: space-between; + /* align-items: center; */ +} + +.logo img { + height: 60px; +} + +.nav-links { + list-style: none; + display: flex; + gap: 25px; + justify-content: center; + align-items: center; + text-align: center; + +} + +.nav-links li a { + text-decoration: none; + color: white; + font-weight: 500; + text-align: center; + cursor: pointer; + +} + +.navbar-transparent { + background-color: transparent; +} + +.navbar-dark { + background-color: white; +} +.navbar-dark .nav-links a{ + color: #38B; +} +.nav-links a { + transition: color 0.3s ease; +} + +.navbar-hidden { + transform: translateY(-100%); + transition: transform 0.4s ease-in-out; +} \ No newline at end of file diff --git a/src/app/navbar/navbar.component.html b/src/app/navbar/navbar.component.html new file mode 100644 index 000000000..7245f54c5 --- /dev/null +++ b/src/app/navbar/navbar.component.html @@ -0,0 +1,20 @@ + diff --git a/src/app/navbar/navbar.component.spec.ts b/src/app/navbar/navbar.component.spec.ts new file mode 100644 index 000000000..505cc2ffb --- /dev/null +++ b/src/app/navbar/navbar.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NavbarComponent } from './navbar.component'; + +describe('NavbarComponent', () => { + let component: NavbarComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ NavbarComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(NavbarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/navbar/navbar.component.ts b/src/app/navbar/navbar.component.ts new file mode 100644 index 000000000..0e40215e8 --- /dev/null +++ b/src/app/navbar/navbar.component.ts @@ -0,0 +1,42 @@ +import { Component, HostListener, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-navbar', + templateUrl: './navbar.component.html', + styleUrls: ['./navbar.component.css'] +}) +export class NavbarComponent implements OnInit { + isScrolled = false; + + constructor(private router: Router) {} + + ngOnInit(): void { + this.onScroll(); + } + + @HostListener('window:scroll', []) + onScroll(): void { + const scrollY = window.scrollY || document.documentElement.scrollTop; + this.isScrolled = scrollY > 10; + } + + navigateToSection(sectionId: string): void { + const element = document.getElementById(sectionId); + if (element) { + element.scrollIntoView({ behavior: 'smooth' }); + } else { + this.router.navigate(['/home']).then(() => { + setTimeout(() => { + const el = document.getElementById(sectionId); + if (el) el.scrollIntoView({ behavior: 'smooth' }); + }, 50); + }); + } + } + + login(event: Event): void { + event.preventDefault(); + this.router.navigate(['/auth']); + } +} diff --git a/src/app/petit-cadre/petit-cadre.component.css b/src/app/petit-cadre/petit-cadre.component.css new file mode 100644 index 000000000..7fa7c1271 --- /dev/null +++ b/src/app/petit-cadre/petit-cadre.component.css @@ -0,0 +1,21 @@ +button { + border: 2px solid #01b4ff; + background-color: transparent; + color: #01b4ff; + font-weight: 600; + padding: 10px 25px; + border-radius: 25px; + font-size: 16px; + cursor: pointer; + transition: background-color 0.3s ease, color 0.3s ease; +} + +button.active { + background-color: #01b4ff; + color: white; +} + +button:hover { + background-color: #01b4ff; + color: white; +} diff --git a/src/app/petit-cadre/petit-cadre.component.html b/src/app/petit-cadre/petit-cadre.component.html new file mode 100644 index 000000000..b82941cec --- /dev/null +++ b/src/app/petit-cadre/petit-cadre.component.html @@ -0,0 +1,5 @@ + diff --git a/src/app/petit-cadre/petit-cadre.component.spec.ts b/src/app/petit-cadre/petit-cadre.component.spec.ts new file mode 100644 index 000000000..e272db61b --- /dev/null +++ b/src/app/petit-cadre/petit-cadre.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PetitCadreComponent } from './petit-cadre.component'; + +describe('PetitCadreComponent', () => { + let component: PetitCadreComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PetitCadreComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(PetitCadreComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/petit-cadre/petit-cadre.component.ts b/src/app/petit-cadre/petit-cadre.component.ts new file mode 100644 index 000000000..2d79dbe42 --- /dev/null +++ b/src/app/petit-cadre/petit-cadre.component.ts @@ -0,0 +1,17 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'app-petit-cadre', + templateUrl: './petit-cadre.component.html', + styleUrls: ['./petit-cadre.component.css'] +}) +export class PetitCadreComponent { + @Input() label: string = ''; + @Input() active: boolean = false; + @Output() clicked = new EventEmitter(); + + onClick() { + this.clicked.emit(); + } +} + diff --git a/src/app/products/products.component.css b/src/app/products/products.component.css new file mode 100644 index 000000000..355c05214 --- /dev/null +++ b/src/app/products/products.component.css @@ -0,0 +1,66 @@ +.nav{ + width: 100%; + margin-bottom: 50px; +} +.products-grid { + /* width: 85%; jai rajoute hethy pour le truc de 3 containers */ + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 20px; + padding: 20px; + + /* margin-left: 100px; jai rajoute hethy pour le truc de 3 containers */ +} +.carousel-et-nav{ + background-image: url('assets/img/infinite-loop-03.jpg'); +} + +.body { + margin-bottom: 100px; + padding: 0; + background-color: #ffffff; + background-repeat: no-repeat; + background-size: cover; + background-position:center center; + height: 100%; +} +.filter-buttons { + display: flex; + gap: 15px; + justify-content: center; + margin-top: 20px; + margin-bottom: 40px; +} +.description-it { + max-width: 800px; + margin: 0 auto 40px auto; + padding: 20px; + text-align: center; + font-size: 1.1rem; + color: #333; + background-color: #f4f9ff; + border-radius: 12px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); +} + +.btn-formulaire-it { + display: inline-block; + margin-top: 20px; + padding: 12px 24px; + background: linear-gradient(to right, #42a5f5, #1e88e5); + color: white; + text-decoration: none; + font-weight: 600; + border-radius: 30px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + transition: background 0.3s ease; +} + +.btn-formulaire-it:hover { + transform: translateY(-5px); + transition: transform 0.25s ease-in-out; + background: linear-gradient(to right, #1e88e5, #1565c0); +} + + diff --git a/src/app/products/products.component.html b/src/app/products/products.component.html new file mode 100644 index 000000000..0af93980b --- /dev/null +++ b/src/app/products/products.component.html @@ -0,0 +1,66 @@ + +
+
+ + +
+ +
+ +
+ + +
+
+

+ Nous développons et intégrons des solutions IT qui renforcent les opérations, + améliorent la performance et accélèrent la transformation digitale des entreprises. +

+ + 👉 Demander une consultation IT + +
+ + +
+ + +
+ +
+ + +
+ +
+
+
+ + + diff --git a/src/app/products/products.component.spec.ts b/src/app/products/products.component.spec.ts new file mode 100644 index 000000000..623e5feff --- /dev/null +++ b/src/app/products/products.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProductsComponent } from './products.component'; + +describe('ProductsComponent', () => { + let component: ProductsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ProductsComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ProductsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/products/products.component.ts b/src/app/products/products.component.ts new file mode 100644 index 000000000..024caa592 --- /dev/null +++ b/src/app/products/products.component.ts @@ -0,0 +1,66 @@ +import { Component ,HostListener, OnInit} from '@angular/core'; +import { ProduitAvecDevisService, ProduitAvecDevis } from '../Services/ProduitAvecDevisService'; +import { ProduitSansDevisService, ProduitSansDevis } from '../Services/ProduitSansDevisService'; + + +@Component({ + selector: 'app-products', + templateUrl: './products.component.html', + styleUrls: ['./products.component.css'] +}) +export class ProductsComponent implements OnInit { + isScrolled = false; + + @HostListener('window:scroll', []) + onWindowScroll() { + const offset = window.pageYOffset || document.documentElement.scrollTop; + this.isScrolled = offset > 300; + } + + allProduitsAvecDevis: ProduitAvecDevis[] = []; + allProduitsNodevis:ProduitSansDevis[] = []; + + produits_nodevis : ProduitSansDevis[] = []; + produits_avecdevis : ProduitAvecDevis[] = []; + + constructor(private produitAvecDevisService: ProduitAvecDevisService,private produitSansDevisService: ProduitSansDevisService) {} + + petitsCadres = [ + { label: 'Tous', active: true }, + { label: 'GPS Trackers', active: false }, + { label: 'Solutions IoT', active: false }, + { label: 'Solutions IT', active: false } + ]; + ngOnInit(): void { + this.produitAvecDevisService.getAllProduits().subscribe(data => { + this.allProduitsAvecDevis = data; + this.produits_avecdevis = [...data]; + }); + this.produitSansDevisService.getAllProduits().subscribe(data => { + this.allProduitsNodevis = data; + this.produits_nodevis = [...data]; + }); + } + setActive(label: string) { + this.petitsCadres.forEach(c => c.active = (c.label === label)); + + if (label === 'Tous') { + this.produits_nodevis = [...this.allProduitsNodevis]; + this.produits_avecdevis = [...this.allProduitsAvecDevis]; + } else if (label === 'GPS Trackers') { + this.produits_nodevis = this.allProduitsNodevis.filter(p => p.categorie.toUpperCase().includes('GPS')); + this.produits_avecdevis = []; + } else if (label === 'Solutions IoT') { + this.produits_avecdevis = this.allProduitsAvecDevis.filter(p => p.categorie.toUpperCase().includes('IOT')); + this.produits_nodevis = []; + } else if (label === 'Solutions IT') { + this.produits_avecdevis = []; + this.produits_nodevis = []; + } + } + isSolutionsITSelected(): boolean { + return this.petitsCadres.find(c => c.label === 'Solutions IT')?.active || false; +} + +} + diff --git a/src/app/register/register.component.html b/src/app/register/register.component.html new file mode 100644 index 000000000..3fe6c9c45 --- /dev/null +++ b/src/app/register/register.component.html @@ -0,0 +1,49 @@ + + + + + Auth Component + + + + + + +
+
+ + +
+
+
+

{{ 'AUTH.OVERLAY.LEFT.TITLE' | translate }}

+

{{ 'AUTH.OVERLAY.LEFT.TEXT' | translate }}

+ +
+
+

{{ 'AUTH.OVERLAY.RIGHT.TITLE' | translate }}

+

{{ 'AUTH.OVERLAY.RIGHT.TEXT' | translate }}

+ +
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/app/register/register.component.scss b/src/app/register/register.component.scss new file mode 100644 index 000000000..76ab16ad3 --- /dev/null +++ b/src/app/register/register.component.scss @@ -0,0 +1,287 @@ +@import url('https://fonts.googleapis.com/css?family=Montserrat:400,800'); + +.auth-component * { + box-sizing: border-box; + +} +.auth-component { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + background-color: white; +} + +.auth-box { + width: 1000px; + max-width: 95%; + min-height: 600px; + background-color: #fff; + border-radius: 20px; + box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), + 0 10px 10px rgba(0, 0, 0, 0.22); + display: flex; + position: relative; + overflow: hidden; +} + +.auth-component body { + background:white; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + font-family: 'Montserrat', sans-serif; + height: 200vh; + margin: -20px 0 50px; + +} + +.auth-component h1 { + font-weight: bold; + margin: 0; +} + +.auth-component h2 { + text-align: center; +} + +.auth-component p { + font-size: 14px; + font-weight: 100; + line-height: 20px; + letter-spacing: 0.5px; + margin: 20px 0 30px; +} + +.auth-component span { + font-size: 12px; +} + +.auth-component a { + color: #333; + font-size: 14px; + text-decoration: none; + margin: 15px 0; +} + +.auth-component button { + border-radius: 20px; + border: 1px solid #369; + background-color: #369; + color: #FFFFFF; + font-size: 12px; + font-weight: bold; + padding: 12px 45px; + letter-spacing: 1px; + text-transform: uppercase; + transition: transform 80ms ease-in; +} + +.auth-component .overlay { + background: #38B; + background: -webkit-linear-gradient(to right, #357, #37A); + background: linear-gradient(to right, #357, #37A); + background-repeat: no-repeat; + background-size: cover; + background-position: 0 0; + color: #FFFFFF; + position: relative; + left: -100%; + height: 100%; + width: 200%; + transform: translateX(0); + transition: transform 0.6s ease-in-out; +} + + +.auth-component button:active { + transform: scale(0.95); +} + +.auth-component button:focus { + outline: none; +} + +.auth-component button.ghost { + background-color: transparent; + border-color: #FFFFFF; +} + +.auth-component form { + background-color: #FFFFFF; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + padding: 0 50px; + height: 100%; + text-align: center; +} + +.auth-component input { + background-color: #eee; + border: none; + padding: 12px 15px; + margin: 8px 0; + width: 100%; +} + +.auth-component .auth-box { + background-color: #fff; + border-radius: 10px; + box-shadow: 0 14px 28px rgba(0,0,0,0.25), + 0 10px 10px rgba(0,0,0,0.22); + position: relative; + overflow: hidden; + width: 768px; + max-width: 100%; + min-height: 480px; +} + +.auth-component .form-auth-box { + position: absolute; + top: 0; + height: 100%; + transition: all 0.6s ease-in-out; +} + +.auth-component .sign-in-auth-box { + left: 0; + width: 50%; + z-index: 2; +} + +.auth-component .auth-box.right-panel-active .sign-in-auth-box { + transform: translateX(100%); +} + +.auth-component .sign-up-auth-box { + left: 0; + width: 50%; + opacity: 1; + z-index: 1; +} + +.auth-component .auth-box.right-panel-active .sign-up-auth-box { + transform: translateX(100%); + opacity: 1; + z-index: 5; + animation: show 0.6s; +} + +@keyframes show { + 0%, 49.99% { + opacity: 0; + z-index: 1; + } + + 50%, 100% { + opacity: 1; + z-index: 5; + } +} + +.auth-component .overlay-auth-box { + position: absolute; + top: 0; + left: 50%; + width: 50%; + height: 100%; + overflow: hidden; + transition: transform 0.6s ease-in-out; + z-index: 100; +} + +.auth-component .auth-box.right-panel-active .overlay-auth-box{ + transform: translateX(-100%); +} + + +.auth-component .auth-box.right-panel-active .overlay { + transform: translateX(50%); +} + +.auth-component .overlay-panel { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + padding: 0 40px; + text-align: center; + top: 0; + height: 100%; + width: 50%; + transform: translateX(0); + transition: transform 0.6s ease-in-out; +} + +.auth-component .overlay-left { + transform: translateX(-20%); +} + +.auth-component .auth-box.right-panel-active .overlay-left { + transform: translateX(0); +} + +.auth-component .overlay-right { + right: 0; + transform: translateX(0); +} + +.auth-component .auth-box.right-panel-active .overlay-right { + transform: translateX(20%); +} + +.auth-component .social-auth-box { + margin: 20px 0; +} + +.auth-component .social-auth-box a { + border: 1px solid #DDDDDD; + border-radius: 50%; + display: inline-flex; + justify-content: center; + align-items: center; + margin: 0 5px; + height: 40px; + width: 40px; +} +.back-arrow { + position: fixed; + top: 60px; + left: 20px; + font-size: 24px; + color: #007bff; + text-decoration: none; + z-index: 1000; + background: rgba(255, 255, 255, 0.8); + padding: 10px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + + @media (max-width: 768px) { + top: 40px; + left: 10px; + font-size: 20px; + width: 35px; + height: 35px; + } + + &:hover { + color: #0056b3; + background: rgba(255, 255, 255, 1); + } +} +.auth-component input::placeholder { + color: #369; + opacity: 1; +} + +input:-ms-input-placeholder { color: #369; } +input::-ms-input-placeholder { color: #369; } \ No newline at end of file diff --git a/src/app/register/register.component.spec.ts b/src/app/register/register.component.spec.ts new file mode 100644 index 000000000..f97553378 --- /dev/null +++ b/src/app/register/register.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RegisterComponent } from './register.component'; + +describe('RegisterComponent', () => { + let component: RegisterComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ RegisterComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(RegisterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/register/register.component.ts b/src/app/register/register.component.ts new file mode 100644 index 000000000..b66d5984c --- /dev/null +++ b/src/app/register/register.component.ts @@ -0,0 +1,104 @@ +import { Component, OnInit, AfterViewInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AuthService } from 'app/Services/auth.service'; +import Swal from 'sweetalert2'; + +@Component({ + selector: 'app-register', + templateUrl: './register.component.html', + styleUrls: ['./register.component.scss'] +}) +export class RegisterComponent implements OnInit, AfterViewInit { + signUpName: string = ''; + signUpEmail: string = ''; + + signInEmail: string = ''; + signInPassword: string = ''; + constructor(private authService: AuthService,private router: Router) { } + + ngOnInit(): void { + } + + ngAfterViewInit(): void { + const signUpButton = document.getElementById('signUp'); + const signInButton = document.getElementById('signIn'); + const container = document.getElementById('auth-box'); + + if (signUpButton && signInButton && container) { + signUpButton.addEventListener('click', () => { + container.classList.add("right-panel-active"); + }); + + signInButton.addEventListener('click', () => { + container.classList.remove("right-panel-active"); + }); + } + } + + onSignUp(): void { + const data = { + nom: this.signUpName, + email: this.signUpEmail, + role:0 + }; + + this.authService.signUp(data).subscribe({ + next: () => { + Swal.fire({ + icon: 'success', + title: 'Inscription réussie', + text: 'Un mail contenant le mot de passe sera envoyé.' + }); + }, + error: (err) => { + Swal.fire({ + icon: 'error', + title: 'Erreur', + text: 'Un compte avec cet email existe déjà.' + }); + console.error(err); + } + }); + } + + onSignIn() { + const data = { + email: this.signInEmail, + password: this.signInPassword + }; + + this.authService.signIn(data).subscribe({ + next: (res) => { + localStorage.setItem('token', res.token); + localStorage.setItem('role', res.role); + localStorage.setItem('name', res.nom); + + Swal.fire({ + icon: 'success', + title: 'Connexion réussie', + text: `Bienvenue ${res.nom} !`, + confirmButtonText: 'Continuer' + }).then((result) => { + if (result.isConfirmed) { + if (res.role === 'Client') { + this.router.navigate(['/home']); + } else { + this.router.navigate(['/admin']); + } + } + }); + }, + error: (err) => { + Swal.fire({ + icon: 'error', + title: 'Échec de la connexion', + text: 'Email ou mot de passe incorrect.' + }); + console.error(err); + } + }); +} + + + +} diff --git a/src/app/shared/navbar/navbar.module.ts b/src/app/shared/navbar/navbar.module.ts index 0a76b80ed..375b2849c 100644 --- a/src/app/shared/navbar/navbar.module.ts +++ b/src/app/shared/navbar/navbar.module.ts @@ -2,7 +2,6 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { NavbarComponent } from './navbar.component'; - @NgModule({ imports: [ RouterModule, CommonModule ], declarations: [ NavbarComponent ], diff --git a/src/app/sidebar/sidebar.component.html b/src/app/sidebar/sidebar.component.html index 817b194c6..18fdf2204 100644 --- a/src/app/sidebar/sidebar.component.html +++ b/src/app/sidebar/sidebar.component.html @@ -1,78 +1,14 @@ +