From 64119a956a32322d4df5bb427660a951a9944b9d Mon Sep 17 00:00:00 2001 From: Florian THIERRY Date: Wed, 4 Sep 2024 14:06:10 +0200 Subject: [PATCH] Add "my-publications" page. --- frontend/src/app/app.routes.ts | 4 ++ .../components/header/header.component.html | 23 ++++++-- .../components/header/header.component.scss | 55 ++++++++++++++++++ .../app/components/header/header.component.ts | 15 +++-- .../my-publications.component.html | 11 ++++ .../my-publications.component.scss | 5 ++ .../my-publications.component.ts | 36 ++++++++++++ .../my-publications/my-publications.routes.ts | 7 +++ .../my-publications.service.ts | 57 +++++++++++++++++++ 9 files changed, 200 insertions(+), 13 deletions(-) create mode 100644 frontend/src/app/pages/my-publications/my-publications.component.html create mode 100644 frontend/src/app/pages/my-publications/my-publications.component.scss create mode 100644 frontend/src/app/pages/my-publications/my-publications.component.ts create mode 100644 frontend/src/app/pages/my-publications/my-publications.routes.ts create mode 100644 frontend/src/app/pages/my-publications/my-publications.service.ts diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index d89116b..e620ba4 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -25,6 +25,10 @@ export const routes: Routes = [ path: 'publications', loadComponent: () => import('./pages/search-publications/search-publications.component').then(module => module.SearchPublicationsComponent) }, + { + path: 'my-publications', + loadChildren: () => import('./pages/my-publications/my-publications.routes').then(module => module.ROUTES) + }, { path: '**', loadComponent: () => import('./pages/home/home.component').then(module => module.HomeComponent) diff --git a/frontend/src/app/components/header/header.component.html b/frontend/src/app/components/header/header.component.html index df0f00c..5421b68 100644 --- a/frontend/src/app/components/header/header.component.html +++ b/frontend/src/app/components/header/header.component.html @@ -13,11 +13,24 @@
- - Disconnect - - + @if (isAuthenticated) { + + + + + } @else { Login - + }
\ No newline at end of file diff --git a/frontend/src/app/components/header/header.component.scss b/frontend/src/app/components/header/header.component.scss index ab8db28..b325f3d 100644 --- a/frontend/src/app/components/header/header.component.scss +++ b/frontend/src/app/components/header/header.component.scss @@ -139,10 +139,65 @@ $headerHeight: 3.5em; background-color: #5c6bc0; } } + + button { + display: flex; + justify-content: center; + align-items: center; + min-width: 2.5em; + color: white; + margin: 0.5em 0.5em; + border-radius: 10em; + text-decoration: none; + padding: 0; + background-color: #3f51b5; + transition: background-color .2s ease-in-out; + + &:hover { + background-color: #5c6bc0; + } + + mat-icon { + margin: 0; + } + } } } } app-side-menu { height: 100%; +} + +.authenticated-user-menu { + display: flex; + flex-direction: column; + padding: 0.2em 0; + + a { + flex: 1; + display: flex; + flex-direction: row; + align-items: center; + text-decoration: none; + background-color: white; + color: black; + padding: 1em; + gap: .5em; + transition: background-color .2s ease-in-out, color .2s ease-in-out; + + &:hover { + background-color: #5c6bc0; + color: white; + } + + &.disconnection { + color: red; + + &:hover { + background-color: red; + color: white; + } + } + } } \ No newline at end of file diff --git a/frontend/src/app/components/header/header.component.ts b/frontend/src/app/components/header/header.component.ts index 95ef6ef..66ff0b0 100644 --- a/frontend/src/app/components/header/header.component.ts +++ b/frontend/src/app/components/header/header.component.ts @@ -9,6 +9,8 @@ import { MatRippleModule } from '@angular/material/core'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { PublicationsSearchBarComponent } from '../publications-search-bar/publications-search-bar.component'; import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatMenuModule } from '@angular/material/menu'; +import { User } from '../../core/model/User'; @Component({ selector: 'app-header', @@ -17,12 +19,13 @@ import { MatTooltipModule } from '@angular/material/tooltip'; CommonModule, MatButtonModule, MatIconModule, + MatMenuModule, + MatRippleModule, + MatTooltipModule, + PublicationsSearchBarComponent, + ReactiveFormsModule, RouterModule, SideMenuComponent, - MatRippleModule, - ReactiveFormsModule, - PublicationsSearchBarComponent, - MatTooltipModule ], templateUrl: './header.component.html', styleUrl: './header.component.scss', @@ -34,8 +37,4 @@ export class HeaderComponent { get isAuthenticated(): boolean { return this.authenticationService.isAuthenticated(); } - - searchPublications(): void { - - } } diff --git a/frontend/src/app/pages/my-publications/my-publications.component.html b/frontend/src/app/pages/my-publications/my-publications.component.html new file mode 100644 index 0000000..4065830 --- /dev/null +++ b/frontend/src/app/pages/my-publications/my-publications.component.html @@ -0,0 +1,11 @@ +

Your publications

+@if ((isLoading$ | async) === true) { +

Publication loading...

+ +} @else { + @if ((isLoaded$ | async) === true) { + + } @else { +

There is no any publication...

+ } +} \ No newline at end of file diff --git a/frontend/src/app/pages/my-publications/my-publications.component.scss b/frontend/src/app/pages/my-publications/my-publications.component.scss new file mode 100644 index 0000000..b986f35 --- /dev/null +++ b/frontend/src/app/pages/my-publications/my-publications.component.scss @@ -0,0 +1,5 @@ +:host { + display: flex; + flex-direction: column; + align-items: center; +} \ No newline at end of file diff --git a/frontend/src/app/pages/my-publications/my-publications.component.ts b/frontend/src/app/pages/my-publications/my-publications.component.ts new file mode 100644 index 0000000..b595b48 --- /dev/null +++ b/frontend/src/app/pages/my-publications/my-publications.component.ts @@ -0,0 +1,36 @@ +import { Component, inject, OnInit } from "@angular/core"; +import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; +import { MyPublicationsService } from "./my-publications.service"; +import { Observable } from "rxjs"; +import { PublicationListComponent } from "../../components/publication-list/publication-list.component"; +import { Publication } from "../../core/rest-services/publications/model/publication"; +import { CommonModule } from "@angular/common"; + + +@Component({ + selector: 'app-my-component', + standalone: true, + templateUrl: './my-publications.component.html', + styleUrl: './my-publications.component.scss', + imports: [CommonModule, MatProgressSpinnerModule, PublicationListComponent], + providers: [MyPublicationsService] +}) +export class MyPublicationsComponent implements OnInit { + private readonly myPublicationsService = inject(MyPublicationsService); + + get publications$(): Observable { + return this.myPublicationsService.publications$; + } + + get isLoading$(): Observable { + return this.myPublicationsService.isLoading$; + } + + get isLoaded$(): Observable { + return this.myPublicationsService.isLoaded$; + } + + ngOnInit(): void { + this.myPublicationsService.loadAuthenticatedUserPublications(); + } +} \ No newline at end of file diff --git a/frontend/src/app/pages/my-publications/my-publications.routes.ts b/frontend/src/app/pages/my-publications/my-publications.routes.ts new file mode 100644 index 0000000..d0fe316 --- /dev/null +++ b/frontend/src/app/pages/my-publications/my-publications.routes.ts @@ -0,0 +1,7 @@ +import { Route } from "@angular/router"; +import { authenticationGuard } from "../../core/guard/authentication.guard"; +import { MyPublicationsComponent } from "./my-publications.component"; + +export const ROUTES: Route[] = [ + { path: '', component: MyPublicationsComponent, canActivate: [authenticationGuard] } +] \ No newline at end of file diff --git a/frontend/src/app/pages/my-publications/my-publications.service.ts b/frontend/src/app/pages/my-publications/my-publications.service.ts new file mode 100644 index 0000000..e40ee03 --- /dev/null +++ b/frontend/src/app/pages/my-publications/my-publications.service.ts @@ -0,0 +1,57 @@ +import { inject, Injectable } from "@angular/core"; +import { PublicationRestService } from "../../core/rest-services/publications/publication.rest-service"; +import { AuthenticationService } from "../../core/service/authentication.service"; +import { BehaviorSubject, Observable } from "rxjs"; +import { Publication } from "../../core/rest-services/publications/model/publication"; +import { Router } from "@angular/router"; +import { MatSnackBar } from "@angular/material/snack-bar"; + +@Injectable() +export class MyPublicationsService { + private readonly authenticationService = inject(AuthenticationService); + private readonly publicationRestService = inject(PublicationRestService); + private readonly snackBar = inject(MatSnackBar); + private readonly router = inject(Router); + private publicationsSubject = new BehaviorSubject([]); + private isLoadingSubject = new BehaviorSubject(false); + private isLoadedSubject = new BehaviorSubject(false); + + get publications$(): Observable { + return this.publicationsSubject.asObservable(); + } + + get isLoading$(): Observable { + return this.isLoadingSubject.asObservable(); + } + + get isLoaded$(): Observable { + return this.isLoadedSubject.asObservable(); + } + + loadAuthenticatedUserPublications(): void { + const authenticatedUser = this.authenticationService.getAuthenticatedUser(); + if (authenticatedUser) { + this.isLoadingSubject.next(true); + this.isLoadedSubject.next(false); + + const query = `author_id=${authenticatedUser.id}`; + this.publicationRestService.search(query) + .then(publications => { + this.publicationsSubject.next(publications); + }) + .catch(error => { + const errorMessage = 'An error occurred while retrieving your publications...'; + this.snackBar.open(errorMessage); + console.error(errorMessage, error); + }) + .finally(() => { + this.isLoadingSubject.next(false); + this.isLoadedSubject.next(true); + }); + } else { + this.authenticationService.unauthenticate(); + this.snackBar.open('You are unauthenticated. Please, log-in first.', 'Close', { duration: 5000 }); + this.router.navigate(['/login']); + } + } +} \ No newline at end of file