Compare commits
	
		
			No commits in common. "15655a6a2594d5d77aad2f96573e1dc518d6e19c" and "47c769813d8c9011ba53bddcb1a28775a8edb138" have entirely different histories.
		
	
	
		
			15655a6a25
			...
			47c769813d
		
	
		
							
								
								
									
										19
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								README.md
									
									
									
									
									
								
							| @ -2,14 +2,19 @@ | |||||||
| 
 | 
 | ||||||
| ## Liste des améliorations | ## Liste des améliorations | ||||||
| 
 | 
 | ||||||
| - [x] Bug CSS concernant le footer | - [ ] Faire du sessionStorage à la place d'un stockage avec cookie | ||||||
| - [x] Problème d'actualisation des commentaires quand on écrit au moins 2 commentaires à la suite | - [ ] Bouton "Voir les posts de l'utilisateur" à enlever (un seul écrivain donc inutile...) | ||||||
| - [x] Responsive des commentaires | - [ ] Bug CSS concernant le footer | ||||||
| - [x] Garder l'avatar de l'utilisateur quand il met à jour uniquement son pseudo | - [ ] Mauvaise actualisation du pseudo quand on se renomme | ||||||
| - [x] Ne pas avoir à confirmer son mot de passe lors de la connexion | - [ ] Support d'un dark thème (à réflechir...) | ||||||
| - [x] L'avatar s'affiche pas quand on upload un commentaire (il faut recharger la page) | - [ ] Erreur 403 quand on modifie le pseudo mais pas l'image d'un utilisateur | ||||||
|  | - [ ] Garder l'avatar de l'utilisateur quand il met à jour uniquement son pseudo | ||||||
|  | - [ ] Ne pas avoir à confirmer son mot de passe lors de la connexion | ||||||
|  | - [ ] Pouvoir modifier son commentaire | ||||||
|  | - [ ] L'avatar s'affiche pas quand on upload un commentaire (il faut recharger la page) | ||||||
|  | - [ ] Faire des meilleurs modal | ||||||
| - [ ] Terminer l'interface admin | - [ ] Terminer l'interface admin | ||||||
| - [x] Bug (de temps en temps) pour stocker les données utilisateur | - [ ] Bug (de temps en temps) pour stocker les cookies utilisateur | ||||||
| 
 | 
 | ||||||
| pour run le docker : | pour run le docker : | ||||||
| ``` | ``` | ||||||
|  | |||||||
| @ -28,10 +28,14 @@ | |||||||
|               } |               } | ||||||
|             ], |             ], | ||||||
|             "styles": [ |             "styles": [ | ||||||
|               "@angular/material/prebuilt-themes/azure-blue.css", |  | ||||||
|               "src/styles.css" |               "src/styles.css" | ||||||
|             ], |             ], | ||||||
|             "scripts": [] |             "scripts": [], | ||||||
|  |             "server": "src/main.server.ts", | ||||||
|  |             "prerender": true, | ||||||
|  |             "ssr": { | ||||||
|  |               "entry": "server.ts" | ||||||
|  |             } | ||||||
|           }, |           }, | ||||||
|           "configurations": { |           "configurations": { | ||||||
|             "production": { |             "production": { | ||||||
| @ -93,7 +97,6 @@ | |||||||
|               } |               } | ||||||
|             ], |             ], | ||||||
|             "styles": [ |             "styles": [ | ||||||
|               "@angular/material/prebuilt-themes/magenta-violet.css", |  | ||||||
|               "src/styles.css" |               "src/styles.css" | ||||||
|             ], |             ], | ||||||
|             "scripts": [] |             "scripts": [] | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								package.json
									
									
									
									
									
								
							| @ -12,23 +12,20 @@ | |||||||
|   "private": true, |   "private": true, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@angular/animations": "^18.2.0", |     "@angular/animations": "^18.2.0", | ||||||
|     "@angular/cdk": "^18.2.14", |  | ||||||
|     "@angular/common": "^18.2.0", |     "@angular/common": "^18.2.0", | ||||||
|     "@angular/compiler": "^18.2.0", |     "@angular/compiler": "^18.2.0", | ||||||
|     "@angular/core": "^18.2.0", |     "@angular/core": "^18.2.0", | ||||||
|     "@angular/forms": "^18.2.0", |     "@angular/forms": "^18.2.0", | ||||||
|     "@angular/material": "^18.2.14", |  | ||||||
|     "@angular/platform-browser": "^18.2.0", |     "@angular/platform-browser": "^18.2.0", | ||||||
|     "@angular/platform-browser-dynamic": "^18.2.0", |     "@angular/platform-browser-dynamic": "^18.2.0", | ||||||
|     "@angular/platform-server": "^18.2.0", |     "@angular/platform-server": "^18.2.0", | ||||||
|     "@angular/router": "^18.2.0", |     "@angular/router": "^18.2.0", | ||||||
|     "@angular/ssr": "^18.2.18", |     "@angular/ssr": "^18.2.12", | ||||||
|     "@primeng/themes": "^19.1.0", |  | ||||||
|     "express": "^4.18.2", |     "express": "^4.18.2", | ||||||
|     "luxon": "^3.5.0", |     "luxon": "^3.5.0", | ||||||
|     "ngx-cookie-service": "^18.0.0", |     "ngx-cookie-service": "^18.0.0", | ||||||
|     "primeicons": "^7.0.0", |     "primeicons": "^7.0.0", | ||||||
|     "primeng": "^18.0.2", |     "primeng": "^17.18.10", | ||||||
|     "quill": "^2.0.3", |     "quill": "^2.0.3", | ||||||
|     "review-front": "file:", |     "review-front": "file:", | ||||||
|     "rxjs": "~7.8.0", |     "rxjs": "~7.8.0", | ||||||
| @ -36,8 +33,8 @@ | |||||||
|     "zone.js": "~0.14.10" |     "zone.js": "~0.14.10" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@angular-devkit/build-angular": "^18.2.18", |     "@angular-devkit/build-angular": "^18.2.12", | ||||||
|     "@angular/cli": "^18.2.18", |     "@angular/cli": "^18.2.12", | ||||||
|     "@angular/compiler-cli": "^18.2.0", |     "@angular/compiler-cli": "^18.2.0", | ||||||
|     "@types/express": "^4.17.17", |     "@types/express": "^4.17.17", | ||||||
|     "@types/jasmine": "~5.1.0", |     "@types/jasmine": "~5.1.0", | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								public/icon.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/icon.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 32 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/icon.png
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/icon.png
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 133 KiB | 
| @ -1,5 +1,5 @@ | |||||||
| <router-outlet></router-outlet> | <router-outlet></router-outlet> | ||||||
| @if (isBrowser() && (authService.isSessionExpired() && authService.isAuthenticated())) { | @if (isBrowser()) { | ||||||
|   <p-dialog header="ATTENTION !" [modal]="true" [closable]="false" [visible]="isSessionExpired"> |   <p-dialog header="ATTENTION !" [modal]="true" [closable]="false" [visible]="isSessionExpired"> | ||||||
|     <span>Votre session a <strong>expiré</strong> ! Il va falloir se reconnecter.</span> |     <span>Votre session a <strong>expiré</strong> ! Il va falloir se reconnecter.</span> | ||||||
|     <div class="expired-dialog"> |     <div class="expired-dialog"> | ||||||
|  | |||||||
| @ -7,12 +7,12 @@ import {DialogModule} from 'primeng/dialog'; | |||||||
| import {isPlatformBrowser} from '@angular/common'; | import {isPlatformBrowser} from '@angular/common'; | ||||||
| import {Button} from 'primeng/button'; | import {Button} from 'primeng/button'; | ||||||
| import {AuthService} from './auth.service'; | import {AuthService} from './auth.service'; | ||||||
| import {Router, RouterOutlet} from '@angular/router'; | import {CookieService} from 'ngx-cookie-service'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-root', |   selector: 'app-root', | ||||||
|   standalone: true, |   standalone: true, | ||||||
|   imports: [MenubarModule, FloatLabelModule, ToastModule, DialogModule, Button, RouterOutlet], |   imports: [MenubarModule, FloatLabelModule, ToastModule, DialogModule, Button], | ||||||
|   providers: [ |   providers: [ | ||||||
|     MessageService, |     MessageService, | ||||||
|   ], |   ], | ||||||
| @ -23,18 +23,17 @@ export class AppComponent implements OnInit { | |||||||
|   isSessionExpired: boolean = false; |   isSessionExpired: boolean = false; | ||||||
| 
 | 
 | ||||||
|   constructor(@Inject(PLATFORM_ID) private platformId: object, |   constructor(@Inject(PLATFORM_ID) private platformId: object, | ||||||
|               protected authService: AuthService, |               private authService: AuthService, | ||||||
|               private router: Router,) { |               private cookieService: CookieService) { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   isBrowser(): boolean { |   isBrowser(): boolean { | ||||||
|     return isPlatformBrowser(this.platformId) |     return isPlatformBrowser(this.platformId); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   setSessionExpiredFalse(): void { |   setSessionExpiredFalse(): void { | ||||||
|     this.isSessionExpired = false; |     this.isSessionExpired = false; | ||||||
|     this.authService.setSessionExpired(false); |     this.authService.setSessionExpired(false); | ||||||
|     this.router.navigate(['/logout']); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|  | |||||||
| @ -1,12 +1,10 @@ | |||||||
| import {ApplicationConfig, importProvidersFrom, provideZoneChangeDetection} from '@angular/core'; | import {ApplicationConfig, importProvidersFrom, provideZoneChangeDetection} from '@angular/core'; | ||||||
| import {provideRouter} from '@angular/router'; | import {provideRouter} from '@angular/router'; | ||||||
|  | 
 | ||||||
| import {routes} from './app.routes'; | import {routes} from './app.routes'; | ||||||
| import {provideClientHydration} from '@angular/platform-browser'; | import {provideClientHydration} from '@angular/platform-browser'; | ||||||
| import {provideHttpClient, withFetch} from '@angular/common/http'; | import {provideHttpClient, withFetch} from '@angular/common/http'; | ||||||
| import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; | import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; | ||||||
| import {provideAnimationsAsync} from '@angular/platform-browser/animations/async'; |  | ||||||
| import {providePrimeNG} from 'primeng/config'; |  | ||||||
| import {myPreset} from './preset' |  | ||||||
| 
 | 
 | ||||||
| export const appConfig: ApplicationConfig = { | export const appConfig: ApplicationConfig = { | ||||||
|   providers: [ |   providers: [ | ||||||
| @ -14,14 +12,5 @@ export const appConfig: ApplicationConfig = { | |||||||
|     provideRouter(routes), |     provideRouter(routes), | ||||||
|     provideClientHydration(), |     provideClientHydration(), | ||||||
|     provideHttpClient(withFetch()), |     provideHttpClient(withFetch()), | ||||||
|     provideAnimationsAsync(), |     importProvidersFrom([BrowserAnimationsModule])] | ||||||
|     providePrimeNG({ |  | ||||||
|       theme: { |  | ||||||
|         preset: myPreset, |  | ||||||
|         options: { |  | ||||||
|           darkModeSelector: '.my-app-dark' // class css pour activer le dark mode
 |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     }), |  | ||||||
|     importProvidersFrom([BrowserAnimationsModule]), provideAnimationsAsync()] |  | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| import {Injectable} from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import {Author} from './models/author'; | import { CookieService } from 'ngx-cookie-service'; | ||||||
| import {BehaviorSubject} from 'rxjs'; | import { Author } from './models/author'; | ||||||
|  | import { BehaviorSubject } from 'rxjs'; | ||||||
| import {DateTime} from 'luxon'; | import {DateTime} from 'luxon'; | ||||||
| 
 | 
 | ||||||
| @Injectable({ | @Injectable({ | ||||||
| @ -10,38 +11,33 @@ export class AuthService { | |||||||
|   private sessionExpiredSubject = new BehaviorSubject<boolean>(false); |   private sessionExpiredSubject = new BehaviorSubject<boolean>(false); | ||||||
|   sessionExpired$ = this.sessionExpiredSubject.asObservable(); |   sessionExpired$ = this.sessionExpiredSubject.asObservable(); | ||||||
| 
 | 
 | ||||||
|   constructor() { |   constructor(private cookieService: CookieService) { | ||||||
|     this.checkSessionExpiration(); |     this.checkSessionExpiration(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   isAuthenticated(): boolean { |   isAuthenticated(): boolean { | ||||||
|     return sessionStorage.getItem("author") !== null && |     return this.cookieService.check("author") && | ||||||
|       sessionStorage.getItem("token") !== null && |       this.cookieService.check("token") && | ||||||
|       sessionStorage.getItem("token-expiration-date") !== null; |       this.cookieService.check("token-expiration-date") && | ||||||
|  |       this.cookieService.get("author") !== '' && | ||||||
|  |       this.cookieService.get("token-expiration-date") !== '' && | ||||||
|  |       this.cookieService.get("token") !== ''; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   getTokenExpirationDate(): string | null { |   getTokenExpirationDate(): DateTime { | ||||||
|     return sessionStorage.getItem("token-expiration-date"); |     return DateTime.fromISO(this.cookieService.get("token-expiration-date")); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   isSessionExpired(): boolean { |   isSessionExpired(): boolean { | ||||||
|     const tokenExpirationDate = this.getTokenExpirationDate(); |     return this.getTokenExpirationDate() < DateTime.now() && this.isAuthenticated(); | ||||||
|     if (tokenExpirationDate) { |  | ||||||
|       return DateTime.fromISO(tokenExpirationDate) < DateTime.now() && this.isAuthenticated(); |  | ||||||
|     } |  | ||||||
|     return true |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   getAuthenticatedAuthor(): Author | null { |   getAuthenticatedAuthor(): Author { | ||||||
|     const authorStr = sessionStorage.getItem('author') |     return JSON.parse(this.cookieService.get('author')); | ||||||
|     if (authorStr) { |  | ||||||
|       return JSON.parse(authorStr); |  | ||||||
|     } |  | ||||||
|     return null; |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   getAuthenticatedAuthorToken(): string | null{ |   getAuthenticatedAuthorToken(): string { | ||||||
|     return sessionStorage.getItem('token'); |     return this.cookieService.get('token'); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   setSessionExpired(expired: boolean) { |   setSessionExpired(expired: boolean) { | ||||||
|  | |||||||
| @ -1,19 +1,21 @@ | |||||||
| import {Component, EventEmitter, Input, Output} from '@angular/core'; | import {Component, EventEmitter, Input, Output} from '@angular/core'; | ||||||
| import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; | import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; | ||||||
|  | import {InputTextareaModule} from 'primeng/inputtextarea'; | ||||||
| import {Button} from 'primeng/button'; | import {Button} from 'primeng/button'; | ||||||
| import {CommentService} from '../../services/comment.service'; | import {CommentService} from '../../services/comment.service'; | ||||||
| import {Subscription, switchMap} from 'rxjs'; | import {Author} from '../../models/author'; | ||||||
|  | import {Subscription} from 'rxjs'; | ||||||
| import {Comment} from '../../models/comment'; | import {Comment} from '../../models/comment'; | ||||||
| import {MessageService} from 'primeng/api'; | import {MessageService} from 'primeng/api'; | ||||||
| import {NgStyle} from '@angular/common'; | import {NgStyle} from '@angular/common'; | ||||||
| import {AuthService} from '../../auth.service'; | import {AuthService} from '../../auth.service'; | ||||||
| import {AuthorService} from '../../services/author.service'; |  | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-comment-form', |   selector: 'app-comment-form', | ||||||
|   standalone: true, |   standalone: true, | ||||||
|   imports: [ |   imports: [ | ||||||
|     ReactiveFormsModule, |     ReactiveFormsModule, | ||||||
|  |     InputTextareaModule, | ||||||
|     Button, |     Button, | ||||||
|     NgStyle |     NgStyle | ||||||
|   ], |   ], | ||||||
| @ -27,36 +29,25 @@ export class CommentFormComponent { | |||||||
|   @Input({required: true}) postId: bigint = BigInt(1); |   @Input({required: true}) postId: bigint = BigInt(1); | ||||||
|   @Output() commentToEmit = new EventEmitter<Comment>(); |   @Output() commentToEmit = new EventEmitter<Comment>(); | ||||||
|   subs: Subscription[] = []; |   subs: Subscription[] = []; | ||||||
|   createdComment: Comment = {} as Comment; |  | ||||||
| 
 | 
 | ||||||
|   constructor(private commentService: CommentService, |   constructor(private commentService: CommentService, | ||||||
|               private messageService: MessageService, |               private messageService: MessageService, | ||||||
|               private authService: AuthService, |               private authService: AuthService,) { | ||||||
|               private authorService: AuthorService) { |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   onSubmit() { |   onSubmit() { | ||||||
|     let token = this.authService.getAuthenticatedAuthorToken(); |     let token: string = this.authService.getAuthenticatedAuthorToken(); | ||||||
|     let author = this.authService.getAuthenticatedAuthor(); |     let author: Author = this.authService.getAuthenticatedAuthor(); | ||||||
|     if (this.commentForm.valid && author && token && this.commentForm.value.content) { |     if (this.commentForm.valid && author && token && this.commentForm.value.content) { | ||||||
|       // get l'image de profile après avoir créé le commentaire
 |       // get l'image de profile après avoir créé le commentaire
 | ||||||
|       this.subs.push(this.commentService.create(this.commentForm.value.content, this.postId, author.id, token).pipe( |       this.subs.push(this.commentService.create(this.commentForm.value.content, this.postId, author.id, token).subscribe({ | ||||||
|         switchMap((comment: Comment) => { |         next: (comment: Comment) => { | ||||||
|           this.createdComment = { ... this.createdComment } // attention a bien mettre à jour la ref sinon ça casse
 |           comment.authorId = author.id; | ||||||
|           this.createdComment.authorId = author.id; |           comment.authorName = author.name; | ||||||
|           this.createdComment.content = comment.content; |           comment.profilePicture = author.profilePicture; | ||||||
|           this.createdComment.id = comment.id; |           comment.authorRole = author.role; | ||||||
|           this.createdComment.commentDate = comment.commentDate; |  | ||||||
|           this.createdComment.authorName = author.name; |  | ||||||
|           this.createdComment.authorRole = author.role; |  | ||||||
|           this.commentForm.value.content = ""; |           this.commentForm.value.content = ""; | ||||||
|           return this.authorService.getAvatar(author?.id) |           this.commentToEmit.emit(comment); | ||||||
|         }) |  | ||||||
|       ).subscribe({ |  | ||||||
|         next: (profilePicture: string) => { |  | ||||||
|           this.createdComment.profilePicture = profilePicture; |  | ||||||
|           this.commentForm.value.content = ""; |  | ||||||
|           this.commentToEmit.emit(this.createdComment); |  | ||||||
|           this.successMessage("Succès", "Commentaire créé avec succès"); |           this.successMessage("Succès", "Commentaire créé avec succès"); | ||||||
|         }, |         }, | ||||||
|         error: (error) => { |         error: (error) => { | ||||||
|  | |||||||
| @ -25,9 +25,8 @@ export class HeaderComponent { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private initializeMenu(): void { |   private initializeMenu(): void { | ||||||
|     const authenticatedAuthor = this.authService.getAuthenticatedAuthor(); |     if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) { | ||||||
|     if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated() && authenticatedAuthor) { |       this.actualAuthor = this.authService.getAuthenticatedAuthor(); | ||||||
|       this.actualAuthor = authenticatedAuthor; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (this.actualAuthor) { |     if (this.actualAuthor) { | ||||||
|  | |||||||
| @ -1,24 +0,0 @@ | |||||||
| div { |  | ||||||
|   margin-top: 10em; |  | ||||||
|   display: flex; |  | ||||||
|   justify-content: center; |  | ||||||
|   align-items: center; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| img { |  | ||||||
|   animation-name: spin; |  | ||||||
|   animation-duration: 2000ms; |  | ||||||
|   animation-iteration-count: infinite; |  | ||||||
|   animation-timing-function: linear; |  | ||||||
|   max-width: 40%; |  | ||||||
|   max-height: 40%; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @keyframes spin { |  | ||||||
|   from { |  | ||||||
|     transform:rotate(0deg); |  | ||||||
|   } |  | ||||||
|   to { |  | ||||||
|     transform:rotate(360deg); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,3 +0,0 @@ | |||||||
| <div> |  | ||||||
|   <img src="./assets/icon.png" alt="loadign"> |  | ||||||
| </div> |  | ||||||
| @ -1,15 +0,0 @@ | |||||||
| import { Component } from '@angular/core'; |  | ||||||
| import {NgOptimizedImage} from '@angular/common'; |  | ||||||
| 
 |  | ||||||
| @Component({ |  | ||||||
|   selector: 'app-loading', |  | ||||||
|   standalone: true, |  | ||||||
|   imports: [ |  | ||||||
|     NgOptimizedImage |  | ||||||
|   ], |  | ||||||
|   templateUrl: './loading.component.html', |  | ||||||
|   styleUrl: './loading.component.css' |  | ||||||
| }) |  | ||||||
| export class LoadingComponent { |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| @ -1,15 +0,0 @@ | |||||||
| @if (post) { |  | ||||||
|   <p-dialog class="preview-dialog" |  | ||||||
|             header='Prévisualisation de "{{ post.title }}"' |  | ||||||
|             [modal]="true" |  | ||||||
|             [(visible)]="opened" |  | ||||||
|             [closable]="true"> |  | ||||||
|     <app-post-home [title]="post.title" |  | ||||||
|                    [description]="post.description" |  | ||||||
|                    [category]="post.category" |  | ||||||
|                    [date]="post.publicationDate" |  | ||||||
|                    [illustration]="post.illustration" |  | ||||||
|                    [authorProfilePicture]="profilePicture" |  | ||||||
|                    [username]="username"/> |  | ||||||
|   </p-dialog> |  | ||||||
| } |  | ||||||
| @ -1,21 +0,0 @@ | |||||||
| import {Component, Input} from '@angular/core'; |  | ||||||
| import {Dialog} from 'primeng/dialog'; |  | ||||||
| import {PostHomeComponent} from '../../post-home/post-home.component'; |  | ||||||
| import {Post} from '../../../models/post'; |  | ||||||
| 
 |  | ||||||
| @Component({ |  | ||||||
|   selector: 'app-preview-modal', |  | ||||||
|   standalone: true, |  | ||||||
|   imports: [ |  | ||||||
|     Dialog, |  | ||||||
|     PostHomeComponent |  | ||||||
|   ], |  | ||||||
|   templateUrl: './preview-modal.component.html', |  | ||||||
|   styleUrl: './preview-modal.component.css' |  | ||||||
| }) |  | ||||||
| export class PreviewModalComponent { |  | ||||||
|   opened: boolean = true; |  | ||||||
|   @Input({required: true}) post: Post | undefined; |  | ||||||
|   @Input() username: string = ''; |  | ||||||
|   @Input() profilePicture: string = ''; |  | ||||||
| } |  | ||||||
| @ -1,15 +0,0 @@ | |||||||
| @if (post) { |  | ||||||
|   <p-dialog header='Modifier "{{ post.title }}"' |  | ||||||
|             [modal]="true" |  | ||||||
|             [closable]="true" |  | ||||||
|             [(visible)]="opened"> |  | ||||||
|     <app-post-form [actualAuthor]="actualAuthor" |  | ||||||
|                    [postId]="post.id" |  | ||||||
|                    [isUpdateMode]="true" |  | ||||||
|                    [title]="post.title" |  | ||||||
|                    [category]="post.category" |  | ||||||
|                    [description]="post.description" |  | ||||||
|                    [body]="post.body" |  | ||||||
|                    (postUpdate)="onSubmit(post)"/> |  | ||||||
|   </p-dialog> |  | ||||||
| } |  | ||||||
| @ -1,33 +0,0 @@ | |||||||
| import {Component, EventEmitter, Input, Output} from '@angular/core'; |  | ||||||
| import {Dialog} from 'primeng/dialog'; |  | ||||||
| import {PostFormComponent} from '../../post-form/post-form.component'; |  | ||||||
| import {Post} from '../../../models/post'; |  | ||||||
| import {AuthService} from '../../../auth.service'; |  | ||||||
| import {Author} from '../../../models/author'; |  | ||||||
| 
 |  | ||||||
| @Component({ |  | ||||||
|   selector: 'app-update-modal', |  | ||||||
|   standalone: true, |  | ||||||
|   imports: [ |  | ||||||
|     Dialog, |  | ||||||
|     PostFormComponent |  | ||||||
|   ], |  | ||||||
|   templateUrl: './update-modal.component.html', |  | ||||||
|   styleUrl: './update-modal.component.css' |  | ||||||
| }) |  | ||||||
| export class UpdateModalComponent { |  | ||||||
|   actualAuthor: Author | undefined; |  | ||||||
|   opened: boolean = true; |  | ||||||
|   @Input({required: true}) post: Post | undefined; |  | ||||||
|   @Output() updatedPost: EventEmitter<Post> = new EventEmitter<Post>(); |  | ||||||
| 
 |  | ||||||
|   constructor(private authService: AuthService) { |  | ||||||
|     this.authService.getAuthenticatedAuthor(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   onSubmit(updatedPost: Post) { |  | ||||||
|     this.updatedPost.emit(updatedPost); |  | ||||||
|     this.opened = false |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,20 +1,13 @@ | |||||||
| <div> | <div> | ||||||
|   <form [formGroup]="form" (ngSubmit)="onSubmit()"> |   <form [formGroup]="form" (ngSubmit)="onSubmit()"> | ||||||
|     <p-floatlabel variant="on"> |     <label for="title">Titre du post</label> | ||||||
|       <input [(ngModel)]="title" id="title" type="text" pInputText formControlName="title"/> |     <input [(ngModel)]="title" id="title" type="text" pInputText formControlName="title" /> | ||||||
|       <label for="title">Titre du post</label> |  | ||||||
|     </p-floatlabel> |  | ||||||
| 
 | 
 | ||||||
|     <p-floatlabel variant="on"> |     <label for="category">Catégorie du post</label> | ||||||
|       <input [(ngModel)]="category" pInputText id="category" formControlName="category" type="text"/> |     <input [(ngModel)]="category" pInputText id="category" formControlName="category" type="text" /> | ||||||
|       <label for="category">Catégorie du post</label> |  | ||||||
|     </p-floatlabel> |  | ||||||
| 
 |  | ||||||
|     <p-floatlabel variant="on"> |  | ||||||
|       <textarea pTextarea id="desc" rows="5" cols="30" pSize="large" formControlName="description"></textarea> |  | ||||||
|       <label for="desc">Description du post</label> |  | ||||||
|     </p-floatlabel> |  | ||||||
| 
 | 
 | ||||||
|  |     <label for="desc">Description du post</label> | ||||||
|  |     <textarea [(ngModel)]="description" formControlName="description" id="desc" pInputTextarea></textarea> | ||||||
| 
 | 
 | ||||||
|     <label>Image descriptive du post</label> |     <label>Image descriptive du post</label> | ||||||
|     <p-fileUpload |     <p-fileUpload | ||||||
| @ -35,43 +28,7 @@ | |||||||
|     </p-fileUpload> |     </p-fileUpload> | ||||||
| 
 | 
 | ||||||
|     <label>Contenu du post</label> |     <label>Contenu du post</label> | ||||||
|     <p-editor [(ngModel)]="body" formControlName="body" [style]="{ height: '320px' }"> |     <p-editor [(ngModel)]="body" formControlName="body" [modules]="editorModules" [style]="{ height: '320px' }"> | ||||||
|       <ng-template #header> |  | ||||||
|         <span class="ql-formats"> |  | ||||||
| <!--          <button type="button" class="ql-list" value="ordered"></button>--> |  | ||||||
|           <!--          <button type="button" class="ql-bullet" value="bullet"></button>--> |  | ||||||
|           <select class="ql-header"> |  | ||||||
|             <option value="1">Titre 1</option> |  | ||||||
|             <option value="2">Titre 2</option> |  | ||||||
|             <option value="3">Titre 3</option> |  | ||||||
|             <option value="4">Titre 4</option> |  | ||||||
|             <option value="5">Titre 5</option> |  | ||||||
|             <option value="6">Titre 6</option> |  | ||||||
|             <option value="">Normal</option> |  | ||||||
|           </select> |  | ||||||
|             <button class="ql-bold"></button> |  | ||||||
|             <button class="ql-italic"></button> |  | ||||||
|             <button class="ql-underline"></button> |  | ||||||
|             <button class="ql-strike"></button> |  | ||||||
|           <span class="ql-formats"> |  | ||||||
|             <select class="ql-color"></select> |  | ||||||
|             <select class="ql-background"></select> |  | ||||||
|           </span> |  | ||||||
|           <span class="ql-formats"> |  | ||||||
|             <button class="ql-script" value="sub"></button> |  | ||||||
|             <button class="ql-script" value="super"></button> |  | ||||||
|           </span> |  | ||||||
|           <span class="ql-formats"> |  | ||||||
|             <button class="ql-blockquote"></button> |  | ||||||
|             <button class="ql-code-block"></button> |  | ||||||
|           </span> |  | ||||||
|             <button type="button" class="ql-list" value="bullet"></button> |  | ||||||
|             <button type="button" class="ql-list" value="ordered"></button> |  | ||||||
|             <button type="button" class="ql-link" aria-label="Link"></button> |  | ||||||
|             <button type="button" class="ql-image" aria-label="Image"></button> |  | ||||||
|             <button type="button" class="ql-video" aria-label="Video"></button> |  | ||||||
|           </span> |  | ||||||
|       </ng-template> |  | ||||||
|     </p-editor> |     </p-editor> | ||||||
| 
 | 
 | ||||||
|     <p-button |     <p-button | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| import {Component, EventEmitter, Input, OnDestroy, Output} from '@angular/core'; | import {Component, Input, OnDestroy} from '@angular/core'; | ||||||
| import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; | import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; | ||||||
| import {InputTextModule} from 'primeng/inputtext'; | import {InputTextModule} from 'primeng/inputtext'; | ||||||
|  | import {InputTextareaModule} from 'primeng/inputtextarea'; | ||||||
| import {FileSelectEvent, FileUploadModule} from 'primeng/fileupload'; | import {FileSelectEvent, FileUploadModule} from 'primeng/fileupload'; | ||||||
| import {mergeMap, Subscription} from 'rxjs'; | import {mergeMap, Subscription} from 'rxjs'; | ||||||
| import {PostService} from '../../services/post.service'; | import {PostService} from '../../services/post.service'; | ||||||
| @ -10,10 +11,6 @@ import {Router} from '@angular/router'; | |||||||
| import {Author} from '../../models/author'; | import {Author} from '../../models/author'; | ||||||
| import {AuthorService} from '../../services/author.service'; | import {AuthorService} from '../../services/author.service'; | ||||||
| import {AuthService} from '../../auth.service'; | import {AuthService} from '../../auth.service'; | ||||||
| import {Button} from 'primeng/button'; |  | ||||||
| import {Textarea} from 'primeng/textarea'; |  | ||||||
| import {FloatLabel} from 'primeng/floatlabel'; |  | ||||||
| import {Post} from '../../models/post'; |  | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-post-form', |   selector: 'app-post-form', | ||||||
| @ -21,11 +18,9 @@ import {Post} from '../../models/post'; | |||||||
|   imports: [ |   imports: [ | ||||||
|     ReactiveFormsModule, |     ReactiveFormsModule, | ||||||
|     InputTextModule, |     InputTextModule, | ||||||
|  |     InputTextareaModule, | ||||||
|     FileUploadModule, |     FileUploadModule, | ||||||
|     EditorModule, |     EditorModule | ||||||
|     Button, |  | ||||||
|     Textarea, |  | ||||||
|     FloatLabel |  | ||||||
|   ], |   ], | ||||||
|   templateUrl: './post-form.component.html', |   templateUrl: './post-form.component.html', | ||||||
|   styleUrls: ['./post-form.component.css'] |   styleUrls: ['./post-form.component.css'] | ||||||
| @ -38,10 +33,17 @@ export class PostFormComponent implements OnDestroy { | |||||||
|   @Input() category: string = ''; |   @Input() category: string = ''; | ||||||
|   @Input() description: string = ''; |   @Input() description: string = ''; | ||||||
|   @Input() body: string = ''; |   @Input() body: string = ''; | ||||||
|   @Output() postUpdate: EventEmitter<Post> = new EventEmitter(); |  | ||||||
|   subs: Subscription[] = []; |   subs: Subscription[] = []; | ||||||
|   form: FormGroup; |   form: FormGroup; | ||||||
|   uploadedFile: File | undefined; |   uploadedFile: File | undefined; | ||||||
|  |   editorModules = { | ||||||
|  |     toolbar: [ | ||||||
|  |       ['bold', 'italic', 'underline', 'code'], // Styles de texte
 | ||||||
|  |       [{header: [2, false]}], // Permet d'ajouter un `<h2>`
 | ||||||
|  |       [{list: 'ordered'}, {list: 'bullet'}], // Listes
 | ||||||
|  |       ['link', 'image', 'video'], // Ajout de liens et images
 | ||||||
|  |     ], | ||||||
|  |   }; | ||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     private formBuilder: FormBuilder, |     private formBuilder: FormBuilder, | ||||||
| @ -103,13 +105,12 @@ export class PostFormComponent implements OnDestroy { | |||||||
| 
 | 
 | ||||||
|       if (this.isUpdateMode && this.postId) { |       if (this.isUpdateMode && this.postId) { | ||||||
|         this.subs.push( |         this.subs.push( | ||||||
|           this.postService.updatePost(this.postId, postData, this.authService.getAuthenticatedAuthorToken()!).pipe( |           this.postService.updatePost(this.postId, postData, this.authService.getAuthenticatedAuthorToken()).pipe( | ||||||
|             mergeMap((_) => { |             mergeMap((_) => { | ||||||
|               return this.postService.changeIllustration(this.postId, this.uploadedFile, this.authService.getAuthenticatedAuthorToken()!); |               return this.postService.changeIllustration(this.postId, this.uploadedFile, this.authService.getAuthenticatedAuthorToken()); | ||||||
|             }) |             }) | ||||||
|           ).subscribe({ |           ).subscribe({ | ||||||
|             next: (_) => { |             next: (_) => { | ||||||
|               this.postUpdate.emit(_); |  | ||||||
|               this.successMessage('Succès', 'Post mis à jour avec succès') |               this.successMessage('Succès', 'Post mis à jour avec succès') | ||||||
|             }, |             }, | ||||||
|             error: (err) => this.failureMessage('Erreur', err.error.message) |             error: (err) => this.failureMessage('Erreur', err.error.message) | ||||||
| @ -117,11 +118,11 @@ export class PostFormComponent implements OnDestroy { | |||||||
|         ); |         ); | ||||||
|       } else { |       } else { | ||||||
|         this.subs.push( |         this.subs.push( | ||||||
|           this.postService.createPost(postData, this.authService.getAuthenticatedAuthorToken()!).pipe( |           this.postService.createPost(postData, this.authService.getAuthenticatedAuthorToken()).pipe( | ||||||
|             mergeMap(post => |             mergeMap(post => | ||||||
|               this.authorService.attributePost(this.actualAuthor?.id, post.id, this.authService.getAuthenticatedAuthorToken()!).pipe( |               this.authorService.attributePost(this.actualAuthor?.id, post.id, this.authService.getAuthenticatedAuthorToken()).pipe( | ||||||
|                 mergeMap((_) => |                 mergeMap((_) => | ||||||
|                   this.postService.changeIllustration(post.id, this.uploadedFile, this.authService.getAuthenticatedAuthorToken()!), |                   this.postService.changeIllustration(post.id, this.uploadedFile, this.authService.getAuthenticatedAuthorToken()), | ||||||
|                 ) |                 ) | ||||||
|               ) |               ) | ||||||
|             ) |             ) | ||||||
| @ -139,7 +140,6 @@ export class PostFormComponent implements OnDestroy { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private transformYouTubeLinksToIframes(html: string): string { |   private transformYouTubeLinksToIframes(html: string): string { | ||||||
|     // Magie noire
 |  | ||||||
|     return html.replace(/<a[^>]*href="(https?:\/\/(?:www\.)?(youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([a-zA-Z0-9_-]+)[^"]*)".*?<\/a>/g, |     return html.replace(/<a[^>]*href="(https?:\/\/(?:www\.)?(youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([a-zA-Z0-9_-]+)[^"]*)".*?<\/a>/g, | ||||||
|       (_, _url, _prefix, videoId) => { |       (_, _url, _prefix, videoId) => { | ||||||
|         return `<iframe width="560" height="315" src="https://www.youtube.com/embed/${videoId}" frameborder="0" allowfullscreen></iframe>`; |         return `<iframe width="560" height="315" src="https://www.youtube.com/embed/${videoId}" frameborder="0" allowfullscreen></iframe>`; | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ | |||||||
|     <span>{{ category }}</span> |     <span>{{ category }}</span> | ||||||
|     <em>Publié le {{ date | date : "dd/MM/yyyy à HH:mm" }}</em> |     <em>Publié le {{ date | date : "dd/MM/yyyy à HH:mm" }}</em> | ||||||
|     <span class="desc">{{ description }}</span> |     <span class="desc">{{ description }}</span> | ||||||
|     <p-button routerLink="post/{{ postId }}" >Lire la suite</p-button> |     <p-button routerLink="post/{{ postId }}" label="Lire la suite"/> | ||||||
|     <a routerLink="/profile/{{ authorId }}" class="user-profile"> |     <a routerLink="/profile/{{ authorId }}" class="user-profile"> | ||||||
|       @if (authorProfilePicture) { |       @if (authorProfilePicture) { | ||||||
|         <p-avatar image="data:image/jpeg;base64,{{ authorProfilePicture }}" shape="circle" styleClass="mr-2" |         <p-avatar image="data:image/jpeg;base64,{{ authorProfilePicture }}" shape="circle" styleClass="mr-2" | ||||||
|  | |||||||
| @ -4,17 +4,16 @@ import {CardModule} from 'primeng/card'; | |||||||
| import {DatePipe} from '@angular/common'; | import {DatePipe} from '@angular/common'; | ||||||
| import {RouterLink} from '@angular/router'; | import {RouterLink} from '@angular/router'; | ||||||
| import {AvatarModule} from 'primeng/avatar'; | import {AvatarModule} from 'primeng/avatar'; | ||||||
| import {MatButton, MatFabButton} from '@angular/material/button'; |  | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-post-home', |   selector: 'app-post-home', | ||||||
|   standalone: true, |   standalone: true, | ||||||
|   imports: [ |   imports: [ | ||||||
|  |     Button, | ||||||
|     CardModule, |     CardModule, | ||||||
|     DatePipe, |     DatePipe, | ||||||
|     RouterLink, |     RouterLink, | ||||||
|     AvatarModule, |     AvatarModule | ||||||
|     Button, |  | ||||||
|   ], |   ], | ||||||
|   templateUrl: './post-home.component.html', |   templateUrl: './post-home.component.html', | ||||||
|   styleUrl: './post-home.component.css' |   styleUrl: './post-home.component.css' | ||||||
|  | |||||||
| @ -37,7 +37,7 @@ export class RegisterFormComponent implements OnDestroy { | |||||||
|   ]; |   ]; | ||||||
|   subs: Subscription[] = []; |   subs: Subscription[] = []; | ||||||
|   form: FormGroup; |   form: FormGroup; | ||||||
|   actualAuthor: string | undefined; |   actualAuthor: Author | undefined; | ||||||
| 
 | 
 | ||||||
|   constructor(private formBuilder: FormBuilder, |   constructor(private formBuilder: FormBuilder, | ||||||
|               private authorService: AuthorService, |               private authorService: AuthorService, | ||||||
| @ -45,9 +45,8 @@ export class RegisterFormComponent implements OnDestroy { | |||||||
|               private messageService: MessageService, |               private messageService: MessageService, | ||||||
|               private authService: AuthService, |               private authService: AuthService, | ||||||
|   ) { |   ) { | ||||||
|     const authenticatedAuthor = this.authService.getAuthenticatedAuthorToken(); |     if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) { | ||||||
|     if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated() && authenticatedAuthor) { |       this.actualAuthor = this.authService.getAuthenticatedAuthor(); | ||||||
|       this.actualAuthor = authenticatedAuthor; |  | ||||||
|     } |     } | ||||||
|     this.form = this.formBuilder.group({ |     this.form = this.formBuilder.group({ | ||||||
|       username: ['', [Validators.required, Validators.maxLength(255)]], |       username: ['', [Validators.required, Validators.maxLength(255)]], | ||||||
| @ -84,7 +83,7 @@ export class RegisterFormComponent implements OnDestroy { | |||||||
|           this.username, |           this.username, | ||||||
|           this.password, |           this.password, | ||||||
|           this.role, |           this.role, | ||||||
|           this.authService.getAuthenticatedAuthorToken()!).subscribe({ |           this.authService.getAuthenticatedAuthorToken()).subscribe({ | ||||||
|           next: (author: Author) => { |           next: (author: Author) => { | ||||||
|             this.successMessage('Succès', `Auteur ${author.name} créé avec succès`); |             this.successMessage('Succès', `Auteur ${author.name} créé avec succès`); | ||||||
|             this.createdAuthor.emit(author); |             this.createdAuthor.emit(author); | ||||||
|  | |||||||
| @ -7,6 +7,7 @@ import {Subscription, switchMap} from 'rxjs'; | |||||||
| import {AuthorService} from '../../services/author.service'; | import {AuthorService} from '../../services/author.service'; | ||||||
| import {MessageService} from 'primeng/api'; | import {MessageService} from 'primeng/api'; | ||||||
| import {FileSelectEvent, FileUploadModule} from 'primeng/fileupload'; | import {FileSelectEvent, FileUploadModule} from 'primeng/fileupload'; | ||||||
|  | import {CookieService} from 'ngx-cookie-service'; | ||||||
| import {Author} from '../../models/author'; | import {Author} from '../../models/author'; | ||||||
| import {Router} from '@angular/router'; | import {Router} from '@angular/router'; | ||||||
| import {AuthService} from '../../auth.service'; | import {AuthService} from '../../auth.service'; | ||||||
| @ -37,6 +38,7 @@ export class UpdateProfileFormComponent implements OnDestroy { | |||||||
|   constructor(private formBuilder: FormBuilder, |   constructor(private formBuilder: FormBuilder, | ||||||
|               private authorService: AuthorService, |               private authorService: AuthorService, | ||||||
|               private messageService: MessageService, |               private messageService: MessageService, | ||||||
|  |               private cookieService: CookieService, | ||||||
|               private authService: AuthService, |               private authService: AuthService, | ||||||
|               private router: Router, |               private router: Router, | ||||||
|   ) { |   ) { | ||||||
| @ -78,7 +80,7 @@ export class UpdateProfileFormComponent implements OnDestroy { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   onSubmit() { |   onSubmit() { | ||||||
|     const token = this.authService.getAuthenticatedAuthorToken(); |     const token: string = this.authService.getAuthenticatedAuthorToken(); | ||||||
|     if (this.form.valid && token && this.password === this.passwordConfirm) { |     if (this.form.valid && token && this.password === this.passwordConfirm) { | ||||||
|       const newUsername = this.form.value.username; |       const newUsername = this.form.value.username; | ||||||
|       if (this.uploadedFile) { |       if (this.uploadedFile) { | ||||||
| @ -90,7 +92,7 @@ export class UpdateProfileFormComponent implements OnDestroy { | |||||||
|           next: (author: Author) => { |           next: (author: Author) => { | ||||||
|             this.successMessage("Mise à jour réussie", "Profil mit à jour avec succès"); |             this.successMessage("Mise à jour réussie", "Profil mit à jour avec succès"); | ||||||
|             this.updatedAuthorEvent.emit(author); |             this.updatedAuthorEvent.emit(author); | ||||||
|             sessionStorage.setItem('author', JSON.stringify(author)); |             this.cookieService.set('author', JSON.stringify(author)); | ||||||
|             this.router.navigate(['/']); |             this.router.navigate(['/']); | ||||||
|           }, |           }, | ||||||
|           error: (err) => { |           error: (err) => { | ||||||
| @ -102,7 +104,7 @@ export class UpdateProfileFormComponent implements OnDestroy { | |||||||
|           next: (author: Author) => { |           next: (author: Author) => { | ||||||
|             this.successMessage("Mise à jour réussie", "Profil mit à jour avec succès"); |             this.successMessage("Mise à jour réussie", "Profil mit à jour avec succès"); | ||||||
|             this.updatedAuthorEvent.emit(author); |             this.updatedAuthorEvent.emit(author); | ||||||
|             sessionStorage.setItem('author', JSON.stringify(author)); |             this.cookieService.set('author', JSON.stringify(author)); | ||||||
|             this.router.navigate(['/']); |             this.router.navigate(['/']); | ||||||
|           }, |           }, | ||||||
|           error: (err) => { |           error: (err) => { | ||||||
|  | |||||||
| @ -1,13 +1,17 @@ | |||||||
| import {CanActivateFn, Router} from '@angular/router'; | import {CanActivateFn, Router} from '@angular/router'; | ||||||
| import {inject} from '@angular/core'; | import {inject} from '@angular/core'; | ||||||
|  | import {CookieService} from 'ngx-cookie-service'; | ||||||
| import {AuthService} from '../auth.service'; | import {AuthService} from '../auth.service'; | ||||||
| import {Role} from '../models/role'; | import {Role} from '../models/role'; | ||||||
| 
 | 
 | ||||||
| export const adminGuard: CanActivateFn = (route, state) => { | export const adminGuard: CanActivateFn = (route, state) => { | ||||||
|   const router = inject(Router); |   const router = inject(Router); | ||||||
|  |   const cookieService = inject(CookieService); | ||||||
|   const authService: AuthService = inject(AuthService); |   const authService: AuthService = inject(AuthService); | ||||||
|   if ((authService.isAuthenticated() && JSON.parse(sessionStorage.getItem("author")!).role !== Role.ADMIN) || !authService.isAuthenticated()) { | 
 | ||||||
|  |   if ((authService.isAuthenticated() && JSON.parse(cookieService.get("author")).role !== Role.ADMIN) || !authService.isAuthenticated()) { | ||||||
|     router.navigate(['/']); |     router.navigate(['/']); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|   return true; |   return true; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,9 +1,11 @@ | |||||||
| import {CanActivateFn, Router} from '@angular/router'; | import {CanActivateFn, Router} from '@angular/router'; | ||||||
| import {inject} from '@angular/core'; | import {inject} from '@angular/core'; | ||||||
|  | import {CookieService} from 'ngx-cookie-service'; | ||||||
| 
 | 
 | ||||||
| export const authGuard: CanActivateFn = (route, state) => { | export const authGuard: CanActivateFn = (route, state) => { | ||||||
|   const router = inject(Router); |   const router = inject(Router); | ||||||
|   if (sessionStorage.getItem("author") !== null || sessionStorage.getItem("token") !== null) { |   const cookieService = inject(CookieService); | ||||||
|  |   if (cookieService.check("author") || cookieService.check("token")) { | ||||||
|     router.navigate(['/']); |     router.navigate(['/']); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,18 +1,17 @@ | |||||||
| import {CanActivateFn, Router} from '@angular/router'; | import {CanActivateFn, Router} from '@angular/router'; | ||||||
| import {inject} from '@angular/core'; | import {inject} from '@angular/core'; | ||||||
|  | import {CookieService} from 'ngx-cookie-service'; | ||||||
| import {AuthService} from '../auth.service'; | import {AuthService} from '../auth.service'; | ||||||
| import {Role} from '../models/role'; | import {Role} from '../models/role'; | ||||||
| 
 | 
 | ||||||
| export const writerGuard: CanActivateFn = (route, state) => { | export const writerGuard: CanActivateFn = (route, state) => { | ||||||
|   const router = inject(Router); |   const router = inject(Router); | ||||||
|  |   const cookieService = inject(CookieService); | ||||||
|   const authService = inject(AuthService); |   const authService = inject(AuthService); | ||||||
|   const authorStr = sessionStorage.getItem("author"); |  | ||||||
| 
 | 
 | ||||||
|   if (authorStr) { |   if ((authService.isAuthenticated() && JSON.parse(cookieService.get("author")).role !== Role.WRITER) || !authService.isAuthenticated()) { | ||||||
|     if ((authService.isAuthenticated() && JSON.parse(authorStr).role !== Role.WRITER) || !authService.isAuthenticated()) { |     router.navigate(['/']); | ||||||
|       router.navigate(['/']); |  | ||||||
|     } |  | ||||||
|     return true; |  | ||||||
|   } |   } | ||||||
|   return false; | 
 | ||||||
|  |   return true; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -33,7 +33,7 @@ | |||||||
|       </td> |       </td> | ||||||
|       <td>{{ author.role }}</td> |       <td>{{ author.role }}</td> | ||||||
|       <td> |       <td> | ||||||
|         <p-button icon="pi pi-pencil" (click)="openDialog(updateDialogVisibility, rowIndex)" severity="warn" |         <p-button icon="pi pi-pencil" (click)="openDialog(updateDialogVisibility, rowIndex)" severity="warning" | ||||||
|                             label="Modifier"/> |                             label="Modifier"/> | ||||||
|         <p-dialog header='Modifier "{{ author.name }}"' [modal]="true" [(visible)]="updateDialogVisibility[rowIndex]"> |         <p-dialog header='Modifier "{{ author.name }}"' [modal]="true" [(visible)]="updateDialogVisibility[rowIndex]"> | ||||||
|           <app-register-form [adminForm]="true" [username]="author.name"> |           <app-register-form [adminForm]="true" [username]="author.name"> | ||||||
|  | |||||||
| @ -13,6 +13,6 @@ | |||||||
|                      [authorProfilePicture]="post.authorProfilePicture"/> |                      [authorProfilePicture]="post.authorProfilePicture"/> | ||||||
|     } |     } | ||||||
|   } @else { |   } @else { | ||||||
|     <app-loading></app-loading> |     <h1>Aucun post n'a été créé pour l'instant</h1> | ||||||
|   } |   } | ||||||
| </div> | </div> | ||||||
|  | |||||||
| @ -8,7 +8,6 @@ import {PostService} from '../../services/post.service'; | |||||||
| import {PostHomeComponent} from '../../components/post-home/post-home.component'; | import {PostHomeComponent} from '../../components/post-home/post-home.component'; | ||||||
| import {AuthorWithPost} from '../../models/author-with-post'; | import {AuthorWithPost} from '../../models/author-with-post'; | ||||||
| import {AuthService} from '../../auth.service'; | import {AuthService} from '../../auth.service'; | ||||||
| import {LoadingComponent} from '../../components/loading/loading.component'; |  | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-home', |   selector: 'app-home', | ||||||
| @ -18,7 +17,6 @@ import {LoadingComponent} from '../../components/loading/loading.component'; | |||||||
|     HeaderComponent, |     HeaderComponent, | ||||||
|     ToastModule, |     ToastModule, | ||||||
|     PostHomeComponent, |     PostHomeComponent, | ||||||
|     LoadingComponent, |  | ||||||
|   ], |   ], | ||||||
|   templateUrl: './home.component.html', |   templateUrl: './home.component.html', | ||||||
|   styleUrl: './home.component.css' |   styleUrl: './home.component.css' | ||||||
| @ -31,9 +29,9 @@ export class HomeComponent implements OnDestroy { | |||||||
|   constructor( |   constructor( | ||||||
|     private postService: PostService, |     private postService: PostService, | ||||||
|     private authService: AuthService) { |     private authService: AuthService) { | ||||||
|     const authenticatedAuthor = this.authService.getAuthenticatedAuthor(); | 
 | ||||||
|     if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated() && authenticatedAuthor) { |     if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) { | ||||||
|       this.actualAuthor = authenticatedAuthor; |       this.actualAuthor = this.authService.getAuthenticatedAuthor(); | ||||||
|     } else { |     } else { | ||||||
|       this.authService.checkSessionExpiration(); |       this.authService.checkSessionExpiration(); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -5,7 +5,10 @@ | |||||||
|     <label for="username">Nom d'utilisateur</label> |     <label for="username">Nom d'utilisateur</label> | ||||||
|     <input type="text" id="username" pInputText [(ngModel)]="name"/> |     <input type="text" id="username" pInputText [(ngModel)]="name"/> | ||||||
|     <label for="password">Mot de passe</label> |     <label for="password">Mot de passe</label> | ||||||
|     <input type="password" id="password" pInputText [(ngModel)]="password" (keyup.enter)="sendLogins()"/> |     <input type="password" id="password" pInputText [(ngModel)]="password"/> | ||||||
|  |     <label for="confirm-password">Confirmez le mot de passe</label> | ||||||
|  |     <input type="password" id="confirm-password" pInputText | ||||||
|  |            [(ngModel)]="confirmPassword" (keyup.enter)="sendLogins()"/> | ||||||
|     <p-button |     <p-button | ||||||
|       class="send-button" |       class="send-button" | ||||||
|       label="Se connecter" |       label="Se connecter" | ||||||
|  | |||||||
| @ -7,6 +7,7 @@ import {ToastModule} from 'primeng/toast'; | |||||||
| import {MessageService} from 'primeng/api'; | import {MessageService} from 'primeng/api'; | ||||||
| import {Author} from '../../models/author'; | import {Author} from '../../models/author'; | ||||||
| import {Subscription, switchMap} from 'rxjs'; | import {Subscription, switchMap} from 'rxjs'; | ||||||
|  | import {CookieService} from 'ngx-cookie-service'; | ||||||
| import {HeaderComponent} from '../../components/header/header.component'; | import {HeaderComponent} from '../../components/header/header.component'; | ||||||
| import {Router} from '@angular/router'; | import {Router} from '@angular/router'; | ||||||
| import {ConfigurationService} from '../../configuration.service'; | import {ConfigurationService} from '../../configuration.service'; | ||||||
| @ -29,43 +30,56 @@ export class LoginComponent implements OnDestroy { | |||||||
|   name: string = ""; |   name: string = ""; | ||||||
|   actualAuthor: Author | undefined; |   actualAuthor: Author | undefined; | ||||||
|   password: string = ""; |   password: string = ""; | ||||||
|  |   confirmPassword: string = ""; | ||||||
|   subs: Subscription[] = []; |   subs: Subscription[] = []; | ||||||
| 
 | 
 | ||||||
|   constructor(private authorService: AuthorService, |   constructor(private authorService: AuthorService, | ||||||
|               private messageService: MessageService, |               private messageService: MessageService, | ||||||
|  |               private cookieService: CookieService, | ||||||
|               private router: Router, |               private router: Router, | ||||||
|               private configurationService: ConfigurationService,) { |               private configurationService: ConfigurationService,) {} | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   sendLogins(): void { |   sendLogins(): void { | ||||||
|     this.subs.push |     if (this.password === this.confirmPassword) { | ||||||
|     ( |       this.subs.push | ||||||
|       this.authorService.login(this.name, this.password).pipe( |       ( | ||||||
|         switchMap((tokenObj: any) => { |         this.authorService.login(this.name, this.password).pipe( | ||||||
|           // sessionStorage.removeItem('token');
 |           switchMap((tokenObj: any) => { | ||||||
|           sessionStorage.setItem('token', tokenObj.token); |             this.cookieService.delete('token', '/', this.configurationService.getDomain()) | ||||||
|           return this.authorService.me(tokenObj.token) |             this.cookieService.set("token", tokenObj.token, { | ||||||
|         })) |               domain: this.configurationService.getDomain(), | ||||||
|         .subscribe({ |               secure: true, | ||||||
|           next: (author: Author) => { |               path: '/' | ||||||
|             // sessionStorage.removeItem('author');
 |  | ||||||
|             sessionStorage.setItem('author', JSON.stringify(author)); |  | ||||||
|             sessionStorage.setItem('token-expiration-date', DateTime.now().plus({millisecond: this.configurationService.getTokenTTL()}).toISO()) |  | ||||||
|             this.getAuthorCookie(); |  | ||||||
|             this.router.navigate(['/']).then(() => { |  | ||||||
|               this.successMessage('Connecté avec succès', 'Heureux de vous revoir ' + this.actualAuthor?.name) |  | ||||||
|             }); |             }); | ||||||
|           }, |             return this.authorService.me(tokenObj.token) | ||||||
|           error: (err) => this.failureMessage('Erreur de connexion', err.error.message) |           })) | ||||||
|         }) |           .subscribe({ | ||||||
|     ); |             next: (author: Author) => { | ||||||
|  |               this.cookieService.delete('author', '/', this.configurationService.getDomain()) | ||||||
|  |               this.cookieService.set("author", JSON.stringify(author), { | ||||||
|  |                 domain: this.configurationService.getDomain(), | ||||||
|  |                 secure : true, | ||||||
|  |                 path: '/' }); | ||||||
|  |               this.cookieService.set('token-expiration-date', DateTime.now().plus({millisecond: this.configurationService.getTokenTTL()}).toISO(), { | ||||||
|  |                 domain: this.configurationService.getDomain(), | ||||||
|  |                 secure: true, | ||||||
|  |                 path: '/', | ||||||
|  |               }) | ||||||
|  |               this.getAuthorCookie(); | ||||||
|  |               this.router.navigate(['/']).then(() => { | ||||||
|  |                 this.successMessage('Connecté avec succès', 'Heureux de vous revoir ' + this.actualAuthor?.name) | ||||||
|  |               }); | ||||||
|  |             }, | ||||||
|  |             error: (err) => this.failureMessage('Erreur de connexion', err.error.message) | ||||||
|  |           }) | ||||||
|  |       ); | ||||||
|  |     } else { | ||||||
|  |       this.failureMessage('Erreur de connexion', 'Les deux mots de passe ne correspondent pas') | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   getAuthorCookie(): void { |   getAuthorCookie(): void { | ||||||
|     const authorStr = sessionStorage.getItem('author'); |     this.actualAuthor = JSON.parse(this.cookieService.get("author")); | ||||||
|     if (authorStr) { |  | ||||||
|       this.actualAuthor = JSON.parse(authorStr); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   successMessage(summary: string, detail: string): void { |   successMessage(summary: string, detail: string): void { | ||||||
|  | |||||||
| @ -1,7 +1,9 @@ | |||||||
| import {Component, OnInit} from '@angular/core'; | import {Component, OnInit} from '@angular/core'; | ||||||
|  | import {CookieService} from 'ngx-cookie-service'; | ||||||
| import {HeaderComponent} from '../../components/header/header.component'; | import {HeaderComponent} from '../../components/header/header.component'; | ||||||
| import {Router} from '@angular/router'; | import {Router} from '@angular/router'; | ||||||
| import {MessageService} from 'primeng/api'; | import {MessageService} from 'primeng/api'; | ||||||
|  | import {ConfigurationService} from '../../configuration.service'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-logout', |   selector: 'app-logout', | ||||||
| @ -12,16 +14,19 @@ import {MessageService} from 'primeng/api'; | |||||||
|   templateUrl: './logout.component.html', |   templateUrl: './logout.component.html', | ||||||
|   styleUrl: './logout.component.css' |   styleUrl: './logout.component.css' | ||||||
| }) | }) | ||||||
| export class LogoutComponent implements OnInit { | export class LogoutComponent implements OnInit{ | ||||||
| 
 |  | ||||||
|   constructor(private messageService: MessageService, |  | ||||||
|               private router: Router) { |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|  |   constructor(private cookieService: CookieService, | ||||||
|  |               private messageService: MessageService, | ||||||
|  |               private router: Router, | ||||||
|  |               private configurationService: ConfigurationService,) { } | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     sessionStorage.removeItem("author"); |     const routes: string[] = ['/', '/login', '/register', '/logout', '/profile', '/post', '/new-post', '/admin'] | ||||||
|     sessionStorage.removeItem("token"); |     Object.keys(this.cookieService.getAll()).forEach(key => { | ||||||
|     sessionStorage.removeItem("token-expiration-date"); |       routes.forEach(route => { | ||||||
|  |         this.cookieService.delete(key, route, this.configurationService.getDomain()); | ||||||
|  |       }) | ||||||
|  |     }); | ||||||
|     this.router.navigate(['/']).then(() => this.successMessage('Déconnexion', 'Vous avez été deconnecté avec succès')); |     this.router.navigate(['/']).then(() => this.successMessage('Déconnexion', 'Vous avez été deconnecté avec succès')); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -23,10 +23,29 @@ | |||||||
|       <td>{{ post.publicationDate | date: "dd/MM/yyyy à HH:mm" }}</td> |       <td>{{ post.publicationDate | date: "dd/MM/yyyy à HH:mm" }}</td> | ||||||
|       <td>{{ post.description }}</td> |       <td>{{ post.description }}</td> | ||||||
|       <td> |       <td> | ||||||
|         <p-button icon="pi pi-eye" (click)="openDialogPreview(post)" severity="info" label="Prévisualiser"/> |         <p-button icon="pi pi-eye" (click)="openDialog(previewDialogVisibility, rowIndex)" severity="info" | ||||||
|  |                   label="Prévisualiser"/> | ||||||
|  |         <p-dialog class="preview-dialog" header='Prévisualisation de "{{ post.title }}"' [modal]="true" | ||||||
|  |                   [(visible)]="previewDialogVisibility[rowIndex]"> | ||||||
|  |           <app-post-home [title]="post.title" | ||||||
|  |                          [description]="post.description" | ||||||
|  |                          [category]="post.category" | ||||||
|  |                          [date]="post.publicationDate" | ||||||
|  |                          [illustration]="post.illustration"/> | ||||||
|  |         </p-dialog> | ||||||
|       </td> |       </td> | ||||||
|       <td> |       <td> | ||||||
|         <p-button icon="pi pi-pencil" (click)="openDialogUpdate(post)" severity="warn" label="Modifier"/> |         <p-button icon="pi pi-pencil" (click)="openDialog(updateDialogVisibility, rowIndex)" severity="warning" | ||||||
|  |                   label="Modifier"/> | ||||||
|  |         <p-dialog header='Modifier "{{ post.title }}"' [modal]="true" [(visible)]="updateDialogVisibility[rowIndex]"> | ||||||
|  |           <app-post-form [actualAuthor]="actualAuthor" | ||||||
|  |                          [postId]="post.id" | ||||||
|  |                          [isUpdateMode]="true" | ||||||
|  |                          [title]="post.title" | ||||||
|  |                          [category]="post.category" | ||||||
|  |                          [description]="post.description" | ||||||
|  |                          [body]="post.body"/> | ||||||
|  |         </p-dialog> | ||||||
|       </td> |       </td> | ||||||
|       <td> |       <td> | ||||||
|         <p-button icon="pi pi-trash" (click)="openDialog(deleteDialogVisibility, rowIndex)" severity="danger" |         <p-button icon="pi pi-trash" (click)="openDialog(deleteDialogVisibility, rowIndex)" severity="danger" | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import {Component, OnDestroy, ViewContainerRef} from '@angular/core'; | import {Component, OnDestroy} from '@angular/core'; | ||||||
| import {HeaderComponent} from '../../components/header/header.component'; | import {HeaderComponent} from '../../components/header/header.component'; | ||||||
| import {TableModule} from 'primeng/table'; | import {TableModule} from 'primeng/table'; | ||||||
| import {AuthorService} from '../../services/author.service'; | import {AuthorService} from '../../services/author.service'; | ||||||
| @ -14,8 +14,6 @@ import {PostHomeComponent} from '../../components/post-home/post-home.component' | |||||||
| import {PostService} from '../../services/post.service'; | import {PostService} from '../../services/post.service'; | ||||||
| import {PostFormComponent} from "../../components/post-form/post-form.component"; | import {PostFormComponent} from "../../components/post-form/post-form.component"; | ||||||
| import {AuthService} from '../../auth.service'; | import {AuthService} from '../../auth.service'; | ||||||
| import {PreviewModalComponent} from '../../components/modal/preview-modal/preview-modal.component'; |  | ||||||
| import {UpdateModalComponent} from '../../components/modal/update-modal/update-modal.component'; |  | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-my-posts', |   selector: 'app-my-posts', | ||||||
| @ -35,22 +33,18 @@ import {UpdateModalComponent} from '../../components/modal/update-modal/update-m | |||||||
| }) | }) | ||||||
| export class MyPostsComponent implements OnDestroy { | export class MyPostsComponent implements OnDestroy { | ||||||
|   subs: Subscription[] = []; |   subs: Subscription[] = []; | ||||||
|  |   previewDialogVisibility: boolean[] = []; | ||||||
|   updateDialogVisibility: boolean[] = []; |   updateDialogVisibility: boolean[] = []; | ||||||
|   deleteDialogVisibility: boolean[] = []; |   deleteDialogVisibility: boolean[] = []; | ||||||
|   posts: Post[] = []; |   posts: Post[] = []; | ||||||
|   actualAuthor: Author | undefined; |   actualAuthor: Author; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|   constructor(private authService: AuthService, |   constructor(private authService: AuthService, | ||||||
|               private postService: PostService, |               private postService: PostService, | ||||||
|               private viewContainer: ViewContainerRef, |  | ||||||
|               private authorService: AuthorService, |               private authorService: AuthorService, | ||||||
|               private messageService: MessageService) { |               private messageService: MessageService) { | ||||||
|     const authenticatedAuthor = this.authService.getAuthenticatedAuthor(); |     this.actualAuthor = this.authService.getAuthenticatedAuthor(); | ||||||
|     if (authenticatedAuthor) { |  | ||||||
|       this.actualAuthor = authenticatedAuthor; |  | ||||||
|       this.authorService.getAuthorAvatar(this.actualAuthor.id).subscribe(avatar => this.actualAuthor!.profilePicture = avatar); |  | ||||||
|     } |  | ||||||
|     this.updatePosts(); |     this.updatePosts(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -65,9 +59,8 @@ export class MyPostsComponent implements OnDestroy { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   updatePosts(): void { |   updatePosts(): void { | ||||||
|     const authorToken = this.authService.getAuthenticatedAuthorToken() |     if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) { | ||||||
|     if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated() && authorToken) { |       this.authorService.getAuthorsPosts(this.actualAuthor?.id, this.authService.getAuthenticatedAuthorToken()).subscribe({ | ||||||
|       this.authorService.getAuthorsPosts(this.actualAuthor?.id, authorToken).subscribe({ |  | ||||||
|           next: posts => this.posts = posts, |           next: posts => this.posts = posts, | ||||||
|           error: error => this.failureMessage("Erreur", error.error.message), |           error: error => this.failureMessage("Erreur", error.error.message), | ||||||
|         } |         } | ||||||
| @ -78,16 +71,13 @@ export class MyPostsComponent implements OnDestroy { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   deletePost(id: bigint, rowIndex: number) { |   deletePost(id: bigint, rowIndex: number) { | ||||||
|     const authorToken = this.authService.getAuthenticatedAuthorToken() |     this.postService.deletePost(id, this.authService.getAuthenticatedAuthorToken()).subscribe({ | ||||||
|     if (authorToken) { |       next: (_) => { | ||||||
|       this.postService.deletePost(id, authorToken).subscribe({ |         this.updatePosts() | ||||||
|         next: (_) => { |         this.successMessage("Post supprimé", "Ce post a été supprimé avec succès") | ||||||
|           this.updatePosts() |       }, | ||||||
|           this.successMessage("Post supprimé", "Ce post a été supprimé avec succès") |       error: error => this.failureMessage("Erreur", error.error.message), | ||||||
|         }, |     }); | ||||||
|         error: error => this.failureMessage("Erreur", error.error.message), |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
|     this.closeDialog(this.deleteDialogVisibility, rowIndex) |     this.closeDialog(this.deleteDialogVisibility, rowIndex) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -101,24 +91,6 @@ export class MyPostsComponent implements OnDestroy { | |||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   openDialogPreview(post: Post): void { |  | ||||||
|     const modalInstance = this.viewContainer.createComponent(PreviewModalComponent); |  | ||||||
|     modalInstance.setInput("post", post) |  | ||||||
|     modalInstance.setInput("username", this.actualAuthor?.name) |  | ||||||
|     modalInstance.setInput("profilePicture", this.actualAuthor?.profilePicture) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   openDialogUpdate(post: Post): void { |  | ||||||
|     const modalInstance = this.viewContainer.createComponent(UpdateModalComponent); |  | ||||||
|     modalInstance.setInput("post", post) |  | ||||||
|     modalInstance.instance.updatedPost.subscribe(post => { |  | ||||||
|       console.log(this.posts.map(a => a.id).indexOf(post.id)) |  | ||||||
|       this.posts[this.posts.map(a => a.id).indexOf(post.id)] = post |  | ||||||
|       this.posts = [... this.posts] |  | ||||||
|       modalInstance.destroy(); |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   openDialog(dialogBooleanTab: boolean[], index: number) { |   openDialog(dialogBooleanTab: boolean[], index: number) { | ||||||
|     dialogBooleanTab[index] = true; |     dialogBooleanTab[index] = true; | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ import {Component, EventEmitter, OnDestroy} from '@angular/core'; | |||||||
| import {HeaderComponent} from '../../components/header/header.component'; | import {HeaderComponent} from '../../components/header/header.component'; | ||||||
| import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; | import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; | ||||||
| import {InputTextModule} from 'primeng/inputtext'; | import {InputTextModule} from 'primeng/inputtext'; | ||||||
|  | import {InputTextareaModule} from 'primeng/inputtextarea'; | ||||||
| import {FileSelectEvent, FileUploadModule} from 'primeng/fileupload'; | import {FileSelectEvent, FileUploadModule} from 'primeng/fileupload'; | ||||||
| import {mergeMap, Subscription} from 'rxjs'; | import {mergeMap, Subscription} from 'rxjs'; | ||||||
| import {PostService} from '../../services/post.service'; | import {PostService} from '../../services/post.service'; | ||||||
| @ -20,6 +21,7 @@ import {AuthService} from '../../auth.service'; | |||||||
|     HeaderComponent, |     HeaderComponent, | ||||||
|     ReactiveFormsModule, |     ReactiveFormsModule, | ||||||
|     InputTextModule, |     InputTextModule, | ||||||
|  |     InputTextareaModule, | ||||||
|     FileUploadModule, |     FileUploadModule, | ||||||
|     EditorModule, |     EditorModule, | ||||||
|     PostFormComponent, |     PostFormComponent, | ||||||
| @ -28,6 +30,7 @@ import {AuthService} from '../../auth.service'; | |||||||
|   styleUrl: './new-post.component.css' |   styleUrl: './new-post.component.css' | ||||||
| }) | }) | ||||||
| export class NewPostComponent implements OnDestroy { | export class NewPostComponent implements OnDestroy { | ||||||
|  |   isSessionExpired: EventEmitter<boolean> = new EventEmitter<boolean>(); | ||||||
|   subs: Subscription[] = [] |   subs: Subscription[] = [] | ||||||
|   actualAuthor: Author | undefined; |   actualAuthor: Author | undefined; | ||||||
|   uploadedFile: File | undefined; |   uploadedFile: File | undefined; | ||||||
| @ -37,7 +40,7 @@ export class NewPostComponent implements OnDestroy { | |||||||
|               private postService: PostService, |               private postService: PostService, | ||||||
|               private authorService: AuthorService, |               private authorService: AuthorService, | ||||||
|               private messageService: MessageService, |               private messageService: MessageService, | ||||||
|               private authService: AuthService, |               private authService : AuthService, | ||||||
|               private router: Router) { |               private router: Router) { | ||||||
|     this.form = this.formBuilder.group({ |     this.form = this.formBuilder.group({ | ||||||
|       description: ['', [Validators.required, Validators.maxLength(512)]], |       description: ['', [Validators.required, Validators.maxLength(512)]], | ||||||
| @ -46,10 +49,7 @@ export class NewPostComponent implements OnDestroy { | |||||||
|       category: ['', [Validators.required, Validators.maxLength(50)]], |       category: ['', [Validators.required, Validators.maxLength(50)]], | ||||||
|     }); |     }); | ||||||
|     if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) { |     if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) { | ||||||
|       const authenticatedAuthor = this.authService.getAuthenticatedAuthor(); |       this.actualAuthor = this.authService.getAuthenticatedAuthor(); | ||||||
|       if (authenticatedAuthor) { |  | ||||||
|         this.actualAuthor = authenticatedAuthor; |  | ||||||
|       } |  | ||||||
|     } else { |     } else { | ||||||
|       this.authService.checkSessionExpiration(); |       this.authService.checkSessionExpiration(); | ||||||
|     } |     } | ||||||
| @ -72,31 +72,26 @@ export class NewPostComponent implements OnDestroy { | |||||||
|         category: formData.category as string |         category: formData.category as string | ||||||
|       }; |       }; | ||||||
| 
 | 
 | ||||||
|       const authenticatedAuthor = this.authService.getAuthenticatedAuthorToken(); |       this.subs.push( | ||||||
|       if (authenticatedAuthor) { |         this.postService.createPost(postToPost, this.authService.getAuthenticatedAuthorToken()).pipe( | ||||||
|         this.subs.push( |           mergeMap(post => | ||||||
|           this.postService.createPost(postToPost, authenticatedAuthor).pipe( |             this.authorService.attributePost(this.actualAuthor?.id, post.id, this.authService.getAuthenticatedAuthorToken()).pipe( | ||||||
|             mergeMap(post => |               mergeMap((_) => | ||||||
|               this.authorService.attributePost(this.actualAuthor?.id, post.id, authenticatedAuthor).pipe( |                 this.postService.changeIllustration(post.id, this.uploadedFile, this.authService.getAuthenticatedAuthorToken()), | ||||||
|                 mergeMap((_) => |  | ||||||
|                   this.postService.changeIllustration(post.id, this.uploadedFile, authenticatedAuthor), |  | ||||||
|                 ) |  | ||||||
|               ) |               ) | ||||||
|             ) |             ) | ||||||
|           ).subscribe({ |           ) | ||||||
|             next: () => { |         ).subscribe({ | ||||||
|               this.router.navigate(['/']).then(() => { |           next: () => { | ||||||
|                 this.successMessage('Succès', 'Post créé avec succès') |             this.router.navigate(['/']).then(() => { | ||||||
|               }); |               this.successMessage('Succès', 'Post créé avec succès') | ||||||
|             }, |             }); | ||||||
|             error: (err) => { |           }, | ||||||
|               this.failureMessage('Erreur', err.error.message); |           error: (err) => { | ||||||
|             } |             this.failureMessage('Erreur', err.error.message); | ||||||
|           }) |           } | ||||||
|         ); |         }) | ||||||
|       } else { |       ); | ||||||
|         console.error("Profil mal chargé") |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -58,6 +58,16 @@ em { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .commentaires { | ||||||
|  |   margin: auto; | ||||||
|  |   width: 45%; | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   gap: 2rem; | ||||||
|  |   flex-direction: column; | ||||||
|  |   margin-top: 5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .delete-dialog { | .delete-dialog { | ||||||
|   display: flex; |   display: flex; | ||||||
|   justify-content: center; |   justify-content: center; | ||||||
| @ -65,48 +75,10 @@ em { | |||||||
|   gap: 3em; |   gap: 3em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .commentaires { |  | ||||||
|   margin: auto; |  | ||||||
|   margin-top: 5rem; |  | ||||||
|   width: 45%; |  | ||||||
|   display: flex; |  | ||||||
|   flex-direction: column; |  | ||||||
|   gap: 2.5rem; |  | ||||||
|   padding: 0 1rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .comment-div { | .comment-div { | ||||||
|   display: flex; |   display: flex; | ||||||
|   align-items: flex-start; |   align-items: center; | ||||||
|   gap: 1rem; |   gap: 1rem; | ||||||
|   background-color: #f9f9f9; |  | ||||||
|   padding: 1rem; |  | ||||||
|   border-radius: 12px; |  | ||||||
|   box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); |  | ||||||
|   flex-wrap: wrap; |  | ||||||
|   position: relative; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @media screen and (max-width: 1200px) { |  | ||||||
|   .body-content, |  | ||||||
|   .commentaires { |  | ||||||
|     width: 95%; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   .comment-div { |  | ||||||
|     flex-direction: column; |  | ||||||
|     align-items: flex-start; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   .comment-meta-header { |  | ||||||
|     flex-direction: column; |  | ||||||
|     align-items: flex-start; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   .delete-button { |  | ||||||
|     position: static; |  | ||||||
|     align-self: flex-end; |  | ||||||
|     margin-top: 0.5rem; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -49,12 +49,7 @@ export class PostComponent { | |||||||
|               private authService: AuthService,) { |               private authService: AuthService,) { | ||||||
|     this.route.paramMap.subscribe(params => { |     this.route.paramMap.subscribe(params => { | ||||||
|       if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) { |       if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) { | ||||||
|         const authenticatedAuthor = this.authService.getAuthenticatedAuthor(); |         this.actualAuthor = this.authService.getAuthenticatedAuthor(); | ||||||
|         if (authenticatedAuthor) { |  | ||||||
|           this.actualAuthor = authenticatedAuthor; |  | ||||||
|         } else { |  | ||||||
|           console.error('Profil mal chargé'); |  | ||||||
|         } |  | ||||||
|       } else { |       } else { | ||||||
|         this.authService.checkSessionExpiration(); |         this.authService.checkSessionExpiration(); | ||||||
|       } |       } | ||||||
| @ -68,8 +63,15 @@ export class PostComponent { | |||||||
|             next: res => { |             next: res => { | ||||||
|               res.post.body = res.post.body.replace(/ /g, ' ') |               res.post.body = res.post.body.replace(/ /g, ' ') | ||||||
|               this.concernedPost = res.post; |               this.concernedPost = res.post; | ||||||
|               this.comments = res.comment |               this.sortCommentList(res.comment) | ||||||
|               this.sortCommentList() |               this.comments = res.comment.sort((a, b) => { | ||||||
|  |                 if (a.commentDate > b.commentDate) { | ||||||
|  |                   return -1 | ||||||
|  |                 } else if (a.commentDate < b.commentDate) { | ||||||
|  |                   return 1 | ||||||
|  |                 } | ||||||
|  |                 return 0 | ||||||
|  |               }); | ||||||
|               this.comments.forEach(comment => this.commentDeleteDialogMap.set(comment.id, false)); |               this.comments.forEach(comment => this.commentDeleteDialogMap.set(comment.id, false)); | ||||||
|             }, |             }, | ||||||
|             error: err => this.failureMessage("Erreur", err.error.message) |             error: err => this.failureMessage("Erreur", err.error.message) | ||||||
| @ -83,8 +85,7 @@ export class PostComponent { | |||||||
| 
 | 
 | ||||||
|   addNewCommentToList(comment: Comment) { |   addNewCommentToList(comment: Comment) { | ||||||
|     this.comments.push(comment); |     this.comments.push(comment); | ||||||
|     console.log(this.comments) |     this.sortCommentList(this.comments); | ||||||
|     this.sortCommentList(); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   setCommentDialogVisible(commentId: bigint) { |   setCommentDialogVisible(commentId: bigint) { | ||||||
| @ -112,15 +113,15 @@ export class PostComponent { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   sortCommentList() { |   sortCommentList(comments: Comment[]) { | ||||||
|     this.comments = this.comments.sort((a, b) => { |     comments.sort((a, b) => { | ||||||
|       if (a.commentDate > b.commentDate) { |       if (a.commentDate > b.commentDate) { | ||||||
|         return -1 |         return -1 | ||||||
|       } else if (a.commentDate < b.commentDate) { |       } else if (a.commentDate < b.commentDate) { | ||||||
|         return 1 |         return 1 | ||||||
|       } |       } | ||||||
|       return 0 |       return 0 | ||||||
|     }); |     }) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   successMessage(summary: string, detail: string): void { |   successMessage(summary: string, detail: string): void { | ||||||
|  | |||||||
| @ -17,6 +17,7 @@ | |||||||
|           } |           } | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |       <p-button label="Voir les posts de l'utilisateur"></p-button> | ||||||
|       @if (actualAuthor) { |       @if (actualAuthor) { | ||||||
|         @if (concernedAuthor.id === actualAuthor.id) { |         @if (concernedAuthor.id === actualAuthor.id) { | ||||||
|           <p-button label="Mettre à jour les données du profil" (onClick)="updateProfileDialog=true"/> |           <p-button label="Mettre à jour les données du profil" (onClick)="updateProfileDialog=true"/> | ||||||
| @ -29,5 +30,5 @@ | |||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
| } @else { | } @else { | ||||||
|   <app-loading></app-loading> |   <h1>Loading...</h1> | ||||||
| } | } | ||||||
|  | |||||||
| @ -10,7 +10,6 @@ import {Button} from 'primeng/button'; | |||||||
| import {DialogModule} from 'primeng/dialog'; | import {DialogModule} from 'primeng/dialog'; | ||||||
| import {UpdateProfileFormComponent} from '../../components/update-profile-form/update-profile-form.component'; | import {UpdateProfileFormComponent} from '../../components/update-profile-form/update-profile-form.component'; | ||||||
| import {AuthService} from '../../auth.service'; | import {AuthService} from '../../auth.service'; | ||||||
| import {LoadingComponent} from '../../components/loading/loading.component'; |  | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-profile', |   selector: 'app-profile', | ||||||
| @ -22,7 +21,6 @@ import {LoadingComponent} from '../../components/loading/loading.component'; | |||||||
|     Button, |     Button, | ||||||
|     DialogModule, |     DialogModule, | ||||||
|     UpdateProfileFormComponent, |     UpdateProfileFormComponent, | ||||||
|     LoadingComponent, |  | ||||||
|   ], |   ], | ||||||
|   templateUrl: './profile.component.html', |   templateUrl: './profile.component.html', | ||||||
|   styleUrl: './profile.component.css' |   styleUrl: './profile.component.css' | ||||||
| @ -33,6 +31,7 @@ export class ProfileComponent implements OnDestroy { | |||||||
|   authorName: string = ""; |   authorName: string = ""; | ||||||
|   subs: Subscription[] = []; |   subs: Subscription[] = []; | ||||||
|   updateProfileDialog: boolean = false; |   updateProfileDialog: boolean = false; | ||||||
|  |   changePasswordDialog: boolean = false; | ||||||
| 
 | 
 | ||||||
|   constructor(private route: ActivatedRoute, |   constructor(private route: ActivatedRoute, | ||||||
|               private authorService: AuthorService, |               private authorService: AuthorService, | ||||||
| @ -44,12 +43,7 @@ export class ProfileComponent implements OnDestroy { | |||||||
|       })); |       })); | ||||||
|     }) |     }) | ||||||
|     if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) { |     if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) { | ||||||
|       const authenticatedAuthor = this.authService.getAuthenticatedAuthor(); |       this.actualAuthor = this.authService.getAuthenticatedAuthor(); | ||||||
|       if (authenticatedAuthor) { |  | ||||||
|         this.actualAuthor = authenticatedAuthor; |  | ||||||
|       } else { |  | ||||||
|         console.error("Profil mal chargé"); |  | ||||||
|       } |  | ||||||
|     } else { |     } else { | ||||||
|       this.authService.checkSessionExpiration(); |       this.authService.checkSessionExpiration(); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,21 +0,0 @@ | |||||||
| import {definePreset} from '@primeng/themes'; |  | ||||||
| import Aura from '@primeng/themes/aura'; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| export const myPreset = definePreset(Aura, { |  | ||||||
|   semantic: { |  | ||||||
|     primary: { |  | ||||||
|       50: '{indigo.50}', |  | ||||||
|       100: '{indigo.100}', |  | ||||||
|       200: '{indigo.200}', |  | ||||||
|       300: '{indigo.300}', |  | ||||||
|       400: '{indigo.400}', |  | ||||||
|       500: '{indigo.500}', |  | ||||||
|       600: '{indigo.600}', |  | ||||||
|       700: '{indigo.700}', |  | ||||||
|       800: '{indigo.800}', |  | ||||||
|       900: '{indigo.900}', |  | ||||||
|       950: '{indigo.950}' |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| }); |  | ||||||
| @ -62,11 +62,6 @@ export class AuthorService { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   getAvatar(id: string): Observable<string> { |  | ||||||
|     return this.httpClient.get(`${this.apiUrl}/${id}/avatar`, { responseType: 'text' }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   getAuthor(id: string | null): Observable<Author> { |   getAuthor(id: string | null): Observable<Author> { | ||||||
|     if (id) { |     if (id) { | ||||||
|       return this.httpClient.get<Author>(`${this.apiUrl}/${id}`); |       return this.httpClient.get<Author>(`${this.apiUrl}/${id}`); | ||||||
| @ -94,14 +89,6 @@ export class AuthorService { | |||||||
|         'Authorization': `Bearer ${token}` |         'Authorization': `Bearer ${token}` | ||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
|     return this.httpClient.post<Author>(`${this.apiUrl}/register/admin`, { |     return this.httpClient.post<Author>(`${this.apiUrl}/register/admin`, {name: username, password: password, role: role}, httpOptions); | ||||||
|       name: username, |  | ||||||
|       password: password, |  | ||||||
|       role: role |  | ||||||
|     }, httpOptions); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   getAuthorAvatar(id: string) { |  | ||||||
|     return this.httpClient.get<string>(`${this.apiUrl}/${id}/avatar`); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 133 KiB | 
| @ -6,12 +6,10 @@ | |||||||
|   <title>A BON ENTENDEUR</title> |   <title>A BON ENTENDEUR</title> | ||||||
|   <base href="/"> |   <base href="/"> | ||||||
|   <meta name="viewport" content="width=device-width, initial-scale=1"> |   <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||||
|   <link rel="icon" type="image/x-icon" href="icon.png"> |   <link rel="icon" type="image/x-icon" href="icon.jpg"> | ||||||
|   <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet"> |  | ||||||
|   <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> |  | ||||||
| </head> | </head> | ||||||
| 
 | 
 | ||||||
| <body class="mat-typography"> | <body> | ||||||
| <app-root></app-root> | <app-root></app-root> | ||||||
| <footer class="footer"> | <footer class="footer"> | ||||||
|   <p class="footer-creator">Site web réalisé par <strong>Guams</strong>.</p> |   <p class="footer-creator">Site web réalisé par <strong>Guams</strong>.</p> | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| /*@import '../node_modules/primeng/resources/themes/lara-light-indigo/theme.css';*/ | @import '../node_modules/primeng/resources/themes/lara-light-indigo/theme.css'; | ||||||
| @import '../node_modules/primeicons/primeicons.css'; | @import '../node_modules/primeicons/primeicons.css'; | ||||||
| @import '../node_modules/quill/dist/quill.bubble.css'; | @import '../node_modules/quill/dist/quill.bubble.css'; | ||||||
| @import '../node_modules/quill/dist/quill.snow.css'; | @import '../node_modules/quill/dist/quill.snow.css'; | ||||||
| @ -11,6 +11,7 @@ body { | |||||||
|   background-color: #1c1c1c; |   background-color: #1c1c1c; | ||||||
|   color: #f1f1f1; |   color: #f1f1f1; | ||||||
|   padding: 20px; |   padding: 20px; | ||||||
|  |   text-align: center; | ||||||
|   font-size: 14px; |   font-size: 14px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -19,7 +20,6 @@ body { | |||||||
| .footer-follow, | .footer-follow, | ||||||
| .footer-links, | .footer-links, | ||||||
| .footer-copyright { | .footer-copyright { | ||||||
|   text-align: center; |  | ||||||
|   margin: 5px 0; |   margin: 5px 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -36,6 +36,14 @@ app-root { | |||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .footer { | ||||||
|  |   width: 100%; | ||||||
|  |   text-align: center; | ||||||
|  |   padding: 10px; | ||||||
|  |   background: #222; | ||||||
|  |   color: white; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .footer-link { | .footer-link { | ||||||
|   color: #1e90ff; |   color: #1e90ff; | ||||||
|   text-decoration: none; |   text-decoration: none; | ||||||
| @ -45,12 +53,3 @@ app-root { | |||||||
| .footer-link:hover { | .footer-link:hover { | ||||||
|   text-decoration: underline; |   text-decoration: underline; | ||||||
| } | } | ||||||
| 
 |  | ||||||
| html, body { |  | ||||||
|   height: 100%; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| body { |  | ||||||
|   margin: 0; |  | ||||||
|   font-family: Roboto, "Helvetica Neue", sans-serif; |  | ||||||
| } |  | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user