Fixing Angular 21 by migrating all values by signals. (#11)
Some checks failed
Build and Deploy Java Gradle Application / build-and-deploy (push) Failing after 53s
Some checks failed
Build and Deploy Java Gradle Application / build-and-deploy (push) Failing after 53s
This commit was merged in pull request #11.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
|
||||
import { AuthenticationService } from '../../core/service/authentication.service';
|
||||
import { Router } from '@angular/router';
|
||||
import {Component, inject, OnInit} from '@angular/core';
|
||||
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
|
||||
import {AuthenticationService} from '../../core/service/authentication.service';
|
||||
import {Router} from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-disconnection',
|
||||
imports: [MatProgressSpinnerModule],
|
||||
templateUrl: './disconnection.component.html',
|
||||
styleUrl: './disconnection.component.scss'
|
||||
selector: 'app-disconnection',
|
||||
imports: [MatProgressSpinnerModule],
|
||||
templateUrl: './disconnection.component.html',
|
||||
styleUrl: './disconnection.component.scss'
|
||||
})
|
||||
export class DisconnectionComponent implements OnInit {
|
||||
private authenticationService = inject(AuthenticationService);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<h1 i18n>Last publications</h1>
|
||||
@if ((isLoading$ | async) === true) {
|
||||
<h2 i18n>Publications loading...</h2>
|
||||
<mat-spinner></mat-spinner>
|
||||
@if ((isLoading())) {
|
||||
<h2 i18n>Publications loading...</h2>
|
||||
<mat-spinner/>
|
||||
} @else {
|
||||
@if ((publications$ | async) != []) {
|
||||
<app-publication-list [publications$]="publications$"></app-publication-list>
|
||||
} @else {
|
||||
<h2 i18n>No any publication.</h2>
|
||||
}
|
||||
@if (publications(); as publications) {
|
||||
<app-publication-list [publications]="publications"/>
|
||||
} @else {
|
||||
<h2 i18n>No any publication.</h2>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -1,34 +1,26 @@
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { Observable } from 'rxjs';
|
||||
import { PublicationListComponent } from '../../components/publication-list/publication-list.component';
|
||||
import { Publication } from '../../core/rest-services/publications/model/publication';
|
||||
import { HomeService } from './home.service';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import {Component, inject, OnInit} from '@angular/core';
|
||||
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
|
||||
import {PublicationListComponent} from '../../components/publication-list/publication-list.component';
|
||||
import {HomeService} from './home.service';
|
||||
import {CommonModule} from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatProgressSpinnerModule,
|
||||
PublicationListComponent
|
||||
],
|
||||
templateUrl: './home.component.html',
|
||||
styleUrl: './home.component.scss',
|
||||
providers: [HomeService]
|
||||
selector: 'app-home',
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatProgressSpinnerModule,
|
||||
PublicationListComponent
|
||||
],
|
||||
templateUrl: './home.component.html',
|
||||
styleUrl: './home.component.scss',
|
||||
providers: [HomeService]
|
||||
})
|
||||
export class HomeComponent implements OnInit {
|
||||
private homeService = inject(HomeService);
|
||||
|
||||
get isLoading$(): Observable<boolean> {
|
||||
return this.homeService.isLoading$;
|
||||
}
|
||||
|
||||
get publications$(): Observable<Publication[]> {
|
||||
return this.homeService.publications$;
|
||||
}
|
||||
readonly #homeService = inject(HomeService);
|
||||
isLoading = this.#homeService.isLoading;
|
||||
publications = this.#homeService.publications;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.homeService.startLatestPublicationsRetrieving();
|
||||
this.#homeService.startLatestPublicationsRetrieving();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,34 @@
|
||||
import { Injectable, inject } from "@angular/core";
|
||||
import { PublicationRestService } from "../../core/rest-services/publications/publication.rest-service";
|
||||
import { BehaviorSubject, Observable } from "rxjs";
|
||||
import { MatSnackBar } from "@angular/material/snack-bar"
|
||||
import { Publication } from "../../core/rest-services/publications/model/publication";
|
||||
import {inject, Injectable, Signal, signal} from "@angular/core";
|
||||
import {PublicationRestService} from "../../core/rest-services/publications/publication.rest-service";
|
||||
import {MatSnackBar} from "@angular/material/snack-bar"
|
||||
import {Publication} from "../../core/rest-services/publications/model/publication";
|
||||
|
||||
@Injectable()
|
||||
export class HomeService {
|
||||
private publicationRestService = inject(PublicationRestService);
|
||||
private snackBar = inject(MatSnackBar);
|
||||
private publicationRestService = inject(PublicationRestService);
|
||||
private snackBar = inject(MatSnackBar);
|
||||
|
||||
private publicationsSubject = new BehaviorSubject<Publication[]>([]);
|
||||
private isLoadingSubject = new BehaviorSubject<boolean>(false);
|
||||
#publications = signal<Publication[]>([]);
|
||||
#isLoadingSubject = signal<boolean>(false);
|
||||
|
||||
get isLoading$(): Observable<boolean> {
|
||||
return this.isLoadingSubject.asObservable();
|
||||
}
|
||||
get isLoading(): Signal<boolean> {
|
||||
return this.#isLoadingSubject.asReadonly();
|
||||
}
|
||||
|
||||
get publications$(): Observable<Publication[]> {
|
||||
return this.publicationsSubject.asObservable();
|
||||
}
|
||||
get publications(): Signal<Publication[]> {
|
||||
return this.#publications.asReadonly();
|
||||
}
|
||||
|
||||
startLatestPublicationsRetrieving(): void {
|
||||
this.isLoadingSubject.next(true);
|
||||
startLatestPublicationsRetrieving(): void {
|
||||
this.#isLoadingSubject.set(true);
|
||||
|
||||
this.publicationRestService.getLatest()
|
||||
.then(publications => this.publicationsSubject.next(publications))
|
||||
.catch(error => {
|
||||
const errorMessage = $localize`An error occurred while retrieving latest publications...`;
|
||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||
console.error(errorMessage, error);
|
||||
})
|
||||
.finally(() => this.isLoadingSubject.next(false));
|
||||
}
|
||||
}
|
||||
this.publicationRestService.getLatest()
|
||||
.then(publications => this.#publications.set(publications))
|
||||
.catch(error => {
|
||||
const errorMessage = $localize`An error occurred while retrieving latest publications...`;
|
||||
this.snackBar.open(errorMessage, $localize`Close`, {duration: 5000});
|
||||
console.error(errorMessage, error);
|
||||
})
|
||||
.finally(() => this.#isLoadingSubject.set(false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<form [formGroup]="loginForm" (submit)="performLogin()" class="cod-form card" ngNativeValidate>
|
||||
<h1 i18n>Login</h1>
|
||||
<div class="form-field">
|
||||
<mat-icon>mail</mat-icon>
|
||||
<label for="email" i18n>
|
||||
Email address
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
<input type="email" id="email" formControlName="email" autocomplete="email" required />
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<mat-icon>lock</mat-icon>
|
||||
<label for="password" i18n>
|
||||
Password
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
<input type="password" id="password" formControlName="password" required />
|
||||
</div>
|
||||
<div class="actions reversed">
|
||||
<app-submit-button [requestPending]="false" [disabled]="false" i18n>Send</app-submit-button>
|
||||
<a [routerLink]="['/signin']" class="cod-button secondary" matRipple i18n>Create an account</a>
|
||||
</div>
|
||||
</form>
|
||||
<h1 i18n>Login</h1>
|
||||
<div class="form-field">
|
||||
<mat-icon>mail</mat-icon>
|
||||
<label for="email" i18n>
|
||||
Email address
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
<input type="email" id="email" formControlName="email" autocomplete="email" required/>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<mat-icon>lock</mat-icon>
|
||||
<label for="password" i18n>
|
||||
Password
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
<input type="password" id="password" formControlName="password" required/>
|
||||
</div>
|
||||
<div class="actions reversed">
|
||||
<app-submit-button [requestPending]="false" [disabled]="false" i18n>Send</app-submit-button>
|
||||
<a [routerLink]="['/signin']" class="cod-button secondary" matRipple i18n>Create an account</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1em;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { Component, OnDestroy, OnInit, inject } from '@angular/core';
|
||||
import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { Subscription, debounceTime, map } from 'rxjs';
|
||||
import { LoginService } from './login.service';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { SubmitButtonComponent } from "../../components/submit-button/submit-button.component";
|
||||
import { MatRippleModule } from '@angular/material/core';
|
||||
import {Component, inject, OnDestroy, OnInit} from '@angular/core';
|
||||
import {FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||
import {MatSnackBarModule} from '@angular/material/snack-bar';
|
||||
import {debounceTime, map, Subscription} from 'rxjs';
|
||||
import {LoginService} from './login.service';
|
||||
import {MatIconModule} from '@angular/material/icon';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {SubmitButtonComponent} from "../../components/submit-button/submit-button.component";
|
||||
import {MatRippleModule} from '@angular/material/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrl: './login.component.scss',
|
||||
imports: [
|
||||
MatIconModule,
|
||||
MatRippleModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule,
|
||||
SubmitButtonComponent
|
||||
],
|
||||
providers: [LoginService, MatSnackBarModule]
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrl: './login.component.scss',
|
||||
imports: [
|
||||
MatIconModule,
|
||||
MatRippleModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule,
|
||||
SubmitButtonComponent
|
||||
],
|
||||
providers: [LoginService, MatSnackBarModule]
|
||||
})
|
||||
export class LoginComponent implements OnInit, OnDestroy {
|
||||
private loginService = inject(LoginService);
|
||||
@@ -52,10 +52,10 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
this.subscriptions.push(passwordSubscription)
|
||||
|
||||
const stateSubscription = this.loginService.state$
|
||||
.subscribe(state => {
|
||||
this.loginForm.controls['email'].setValue(state.request.email, { emitEvent: false });
|
||||
this.loginForm.controls['password'].setValue(state.request.password, { emitEvent: false });
|
||||
});
|
||||
.subscribe(state => {
|
||||
this.loginForm.controls['email'].setValue(state.request.email, {emitEvent: false});
|
||||
this.loginForm.controls['password'].setValue(state.request.password, {emitEvent: false});
|
||||
});
|
||||
this.subscriptions.push(stateSubscription);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { copy } from '../../core/utils/ObjectUtils';
|
||||
import { FormError } from '../../core/model/FormError';
|
||||
import { UserRestService } from '../../core/rest-services/user/user.rest-service';
|
||||
import { LoginRequest } from '../../core/rest-services/user/model/login.model';
|
||||
import { AuthenticationService } from '../../core/service/authentication.service';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Router } from '@angular/router';
|
||||
import {inject, Injectable} from '@angular/core';
|
||||
import {BehaviorSubject, Observable} from 'rxjs';
|
||||
import {copy} from '../../core/utils/ObjectUtils';
|
||||
import {FormError} from '../../core/model/FormError';
|
||||
import {UserRestService} from '../../core/rest-services/user/user.rest-service';
|
||||
import {LoginRequest} from '../../core/rest-services/user/model/login.model';
|
||||
import {AuthenticationService} from '../../core/service/authentication.service';
|
||||
import {MatSnackBar} from '@angular/material/snack-bar';
|
||||
import {Router} from '@angular/router';
|
||||
|
||||
export interface LoginState {
|
||||
request: LoginRequest;
|
||||
@@ -65,15 +65,15 @@ export class LoginService {
|
||||
.login(state.request)
|
||||
.then((response) => {
|
||||
this.authenticationService.authenticate(response.accessToken, response.refreshToken);
|
||||
this.snackBar.open($localize`Authentication succeeded!`, $localize`Close`, { duration: 5000 });
|
||||
this.snackBar.open($localize`Authentication succeeded!`, $localize`Close`, {duration: 5000});
|
||||
this.router.navigate(['/home']);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
this.snackBar.open($localize`Authentication failed.`, $localize`Close`, { duration: 5000 });
|
||||
this.snackBar.open($localize`Authentication failed.`, $localize`Close`, {duration: 5000});
|
||||
});
|
||||
} else {
|
||||
this.snackBar.open($localize`Please, fill the inputs before send.`, $localize`Close`, { duration: 5000 });
|
||||
this.snackBar.open($localize`Please, fill the inputs before send.`, $localize`Close`, {duration: 5000});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<h1 i18n>Your publications</h1>
|
||||
|
||||
<a [routerLink]="['/publications/new']"
|
||||
class="new-publication"
|
||||
matTooltip="Add a new publication"
|
||||
matTooltipPosition="left"
|
||||
matRipple
|
||||
i18n-matTooltip>
|
||||
+
|
||||
class="new-publication"
|
||||
matTooltip="Add a new publication"
|
||||
matTooltipPosition="left"
|
||||
matRipple
|
||||
i18n-matTooltip>
|
||||
+
|
||||
</a>
|
||||
|
||||
@if ((isLoading$ | async) === true) {
|
||||
<h2 i18n>Publication loading...</h2>
|
||||
<mat-spinner></mat-spinner>
|
||||
@if (isLoading()) {
|
||||
<h2 i18n>Publication loading...</h2>
|
||||
<mat-spinner></mat-spinner>
|
||||
} @else {
|
||||
@if ((isLoaded$ | async) === true) {
|
||||
<app-publication-list [publications$]="publications$"></app-publication-list>
|
||||
} @else {
|
||||
<h2 i18n>There is no any publication...</h2>
|
||||
}
|
||||
}
|
||||
@if (isLoaded()) {
|
||||
<app-publication-list [publications]="publications()"></app-publication-list>
|
||||
} @else {
|
||||
<h2 i18n>There is no any publication...</h2>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
$newPublicationButtonSize: 1.7em;
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.new-publication {
|
||||
position: fixed;
|
||||
border-radius: 10em;
|
||||
background-color: #14A44D;
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
bottom: $newPublicationButtonSize;
|
||||
right: $newPublicationButtonSize;
|
||||
width: $newPublicationButtonSize;
|
||||
height: $newPublicationButtonSize;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 2px 5px 0 rgba(0,0,0,.16),0 2px 10px 0 rgba(0,0,0,.12);
|
||||
transition: background-color .2s ease-in-out;
|
||||
font-size: 2.2em;
|
||||
|
||||
&:hover {
|
||||
background-color: #0e7a3a;
|
||||
}
|
||||
.new-publication {
|
||||
position: fixed;
|
||||
border-radius: 10em;
|
||||
background-color: #14A44D;
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
bottom: $newPublicationButtonSize;
|
||||
right: $newPublicationButtonSize;
|
||||
width: $newPublicationButtonSize;
|
||||
height: $newPublicationButtonSize;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .16), 0 2px 10px 0 rgba(0, 0, 0, .12);
|
||||
transition: background-color .2s ease-in-out;
|
||||
font-size: 2.2em;
|
||||
|
||||
&:hover {
|
||||
background-color: #0e7a3a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,35 @@
|
||||
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";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { MatTooltipModule } from "@angular/material/tooltip";
|
||||
import { MatRippleModule } from "@angular/material/core";
|
||||
import {Component, inject, OnInit} from "@angular/core";
|
||||
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
|
||||
import {MyPublicationsService} from "./my-publications.service";
|
||||
import {PublicationListComponent} from "../../components/publication-list/publication-list.component";
|
||||
import {CommonModule} from "@angular/common";
|
||||
import {RouterModule} from "@angular/router";
|
||||
import {MatTooltipModule} from "@angular/material/tooltip";
|
||||
import {MatRippleModule} from "@angular/material/core";
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-my-component',
|
||||
templateUrl: './my-publications.component.html',
|
||||
styleUrl: './my-publications.component.scss',
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatRippleModule,
|
||||
MatTooltipModule,
|
||||
PublicationListComponent,
|
||||
RouterModule
|
||||
],
|
||||
providers: [MyPublicationsService]
|
||||
selector: 'app-my-component',
|
||||
templateUrl: './my-publications.component.html',
|
||||
styleUrl: './my-publications.component.scss',
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatRippleModule,
|
||||
MatTooltipModule,
|
||||
PublicationListComponent,
|
||||
RouterModule
|
||||
],
|
||||
providers: [MyPublicationsService]
|
||||
})
|
||||
export class MyPublicationsComponent implements OnInit {
|
||||
private readonly myPublicationsService = inject(MyPublicationsService);
|
||||
private readonly myPublicationsService = inject(MyPublicationsService);
|
||||
|
||||
get publications$(): Observable<Publication[]> {
|
||||
return this.myPublicationsService.publications$;
|
||||
}
|
||||
publications = this.myPublicationsService.publications;
|
||||
isLoading = this.myPublicationsService.isLoading;
|
||||
isLoaded = this.myPublicationsService.isLoaded;
|
||||
|
||||
get isLoading$(): Observable<boolean> {
|
||||
return this.myPublicationsService.isLoading$;
|
||||
}
|
||||
|
||||
get isLoaded$(): Observable<boolean> {
|
||||
return this.myPublicationsService.isLoaded$;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.myPublicationsService.loadAuthenticatedUserPublications();
|
||||
}
|
||||
}
|
||||
ngOnInit(): void {
|
||||
this.myPublicationsService.loadAuthenticatedUserPublications();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Route } from "@angular/router";
|
||||
import { authenticationGuard } from "../../core/guard/authentication.guard";
|
||||
import { MyPublicationsComponent } from "./my-publications.component";
|
||||
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] }
|
||||
]
|
||||
{path: '', component: MyPublicationsComponent, canActivate: [authenticationGuard]}
|
||||
]
|
||||
|
||||
@@ -1,57 +1,56 @@
|
||||
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";
|
||||
import {inject, Injectable, Signal, signal} from "@angular/core";
|
||||
import {PublicationRestService} from "../../core/rest-services/publications/publication.rest-service";
|
||||
import {AuthenticationService} from "../../core/service/authentication.service";
|
||||
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<Publication[]>([]);
|
||||
private isLoadingSubject = new BehaviorSubject<boolean>(false);
|
||||
private isLoadedSubject = new BehaviorSubject<boolean>(false);
|
||||
readonly #authenticationService = inject(AuthenticationService);
|
||||
readonly #publicationRestService = inject(PublicationRestService);
|
||||
readonly #snackBar = inject(MatSnackBar);
|
||||
readonly #router = inject(Router);
|
||||
#publications = signal<Publication[]>([]);
|
||||
#isLoading = signal(false);
|
||||
#isLoaded = signal(false);
|
||||
|
||||
get publications$(): Observable<Publication[]> {
|
||||
return this.publicationsSubject.asObservable();
|
||||
get publications(): Signal<Publication[]> {
|
||||
return this.#publications.asReadonly();
|
||||
}
|
||||
|
||||
get isLoading(): Signal<boolean> {
|
||||
return this.#isLoading.asReadonly();
|
||||
}
|
||||
|
||||
get isLoaded(): Signal<boolean> {
|
||||
return this.#isLoaded.asReadonly();
|
||||
}
|
||||
|
||||
loadAuthenticatedUserPublications(): void {
|
||||
const authenticatedUser = this.#authenticationService.getAuthenticatedUser();
|
||||
if (authenticatedUser) {
|
||||
this.#isLoading.set(true);
|
||||
this.#isLoaded.set(false);
|
||||
|
||||
const query = `author_id=${authenticatedUser.id}`;
|
||||
this.#publicationRestService.search(query)
|
||||
.then(publications => {
|
||||
this.#publications.set(publications);
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = $localize`An error occurred while retrieving your publications...`;
|
||||
this.#snackBar.open(errorMessage, $localize`Close`, {duration: 5000});
|
||||
console.error(errorMessage, error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.#isLoading.set(false);
|
||||
this.#isLoaded.set(true);
|
||||
});
|
||||
} else {
|
||||
this.#authenticationService.unauthenticate();
|
||||
this.#snackBar.open($localize`You are unauthenticated. Please, log-in first.`, $localize`Close`, {duration: 5000});
|
||||
this.#router.navigate(['/login']);
|
||||
}
|
||||
|
||||
get isLoading$(): Observable<boolean> {
|
||||
return this.isLoadingSubject.asObservable();
|
||||
}
|
||||
|
||||
get isLoaded$(): Observable<boolean> {
|
||||
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 = $localize`An error occurred while retrieving your publications...`;
|
||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||
console.error(errorMessage, error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoadingSubject.next(false);
|
||||
this.isLoadedSubject.next(true);
|
||||
});
|
||||
} else {
|
||||
this.authenticationService.unauthenticate();
|
||||
this.snackBar.open($localize`You are unauthenticated. Please, log-in first.`, $localize`Close`, { duration: 5000 });
|
||||
this.router.navigate(['/login']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<app-publication-edition
|
||||
title="Creation of a new publication"
|
||||
[publication]="publication"
|
||||
[isSaving$]="isSaving$"
|
||||
(publicationSave)="onPublicationSave($event)"
|
||||
i18n-title>
|
||||
</app-publication-edition>
|
||||
title="Creation of a new publication"
|
||||
[publication]="publication()"
|
||||
[isSaving]="isSaving()"
|
||||
(publicationSave)="onPublicationSave($event)"
|
||||
i18n-title
|
||||
/>
|
||||
|
||||
@@ -1,74 +1,53 @@
|
||||
import { Component, inject, OnInit } from "@angular/core";
|
||||
import { PublicationEditionComponent } from "../../components/publication-edition/publication-edition.component";
|
||||
import { PublicationRestService } from "../../core/rest-services/publications/publication.rest-service";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { MatSnackBar } from "@angular/material/snack-bar";
|
||||
import { BehaviorSubject, Observable, Subscription } from "rxjs";
|
||||
import { Publication } from "../../core/rest-services/publications/model/publication";
|
||||
import { AuthenticationService } from "../../core/service/authentication.service";
|
||||
import { Author } from "../../core/rest-services/publications/model/author";
|
||||
|
||||
import {Component, inject, OnInit, signal} from "@angular/core";
|
||||
import {PublicationEditionComponent} from "../../components/publication-edition/publication-edition.component";
|
||||
import {PublicationRestService} from "../../core/rest-services/publications/publication.rest-service";
|
||||
import {Router} from "@angular/router";
|
||||
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||
import {DEFAULT_PUBLICATION, Publication} from "../../core/rest-services/publications/model/publication";
|
||||
import {AuthenticationService} from "../../core/service/authentication.service";
|
||||
import {Author} from "../../core/rest-services/publications/model/author";
|
||||
|
||||
@Component({
|
||||
selector: 'app-publication-creation',
|
||||
templateUrl: './publication-creation.component.html',
|
||||
styleUrl: './publication-creation.component.scss',
|
||||
imports: [
|
||||
selector: 'app-publication-creation',
|
||||
templateUrl: './publication-creation.component.html',
|
||||
styleUrl: './publication-creation.component.scss',
|
||||
imports: [
|
||||
PublicationEditionComponent
|
||||
]
|
||||
]
|
||||
})
|
||||
export class PublicationCreationComponent implements OnInit {
|
||||
private readonly authenticationService = inject(AuthenticationService);
|
||||
private readonly publicationRestService = inject(PublicationRestService);
|
||||
private readonly router = inject(Router);
|
||||
private readonly snackBar = inject(MatSnackBar);
|
||||
private isSavingSubject = new BehaviorSubject<boolean>(false);
|
||||
private subscriptions: Subscription[] = [];
|
||||
publication!: Publication;
|
||||
readonly #authenticationService = inject(AuthenticationService);
|
||||
readonly #publicationRestService = inject(PublicationRestService);
|
||||
readonly #router = inject(Router);
|
||||
readonly #snackBar = inject(MatSnackBar);
|
||||
isSaving = signal(false);
|
||||
publication = signal<Publication>(DEFAULT_PUBLICATION);
|
||||
|
||||
get isSaving$(): Observable<boolean> {
|
||||
return this.isSavingSubject.asObservable();
|
||||
ngOnInit(): void {
|
||||
const authenticatedUser = this.#authenticationService.getAuthenticatedUser();
|
||||
if (authenticatedUser) {
|
||||
const author: Author = {
|
||||
id: authenticatedUser.id,
|
||||
name: authenticatedUser.pseudo,
|
||||
image: authenticatedUser.photoId ?? ''
|
||||
};
|
||||
const newPublication: Publication = {...DEFAULT_PUBLICATION, author};
|
||||
this.publication.set(newPublication);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
const authenticatedUser = this.authenticationService.getAuthenticatedUser();
|
||||
if (authenticatedUser) {
|
||||
const author: Author = {
|
||||
id: authenticatedUser.id,
|
||||
name: authenticatedUser.pseudo,
|
||||
image: authenticatedUser.photoId ?? ''
|
||||
};
|
||||
this.publication = {
|
||||
id: '',
|
||||
key: '',
|
||||
title: '',
|
||||
text: '',
|
||||
parsedText: '',
|
||||
description: '',
|
||||
creationDate: new Date(),
|
||||
illustrationId: '',
|
||||
categoryId: '',
|
||||
author
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subscriptions.forEach(subscription => subscription?.unsubscribe());
|
||||
}
|
||||
|
||||
onPublicationSave(publication: Publication): void {
|
||||
this.isSavingSubject.next(true);
|
||||
this.publicationRestService.create(publication)
|
||||
.then(() => {
|
||||
this.snackBar.open($localize`Publication created succesfully!`, $localize`Close`, { duration: 5000 });
|
||||
this.router.navigate(['/my-publications']);
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = $localize`An error occured while saving new publication.`;
|
||||
console.error(errorMessage, error);
|
||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||
})
|
||||
.finally(() => this.isSavingSubject.next(false));
|
||||
}
|
||||
}
|
||||
onPublicationSave(publication: Publication): void {
|
||||
this.isSaving.set(true);
|
||||
this.#publicationRestService.create(publication)
|
||||
.then(() => {
|
||||
this.#snackBar.open($localize`Publication created succesfully!`, $localize`Close`, {duration: 5000});
|
||||
this.#router.navigate(['/my-publications']);
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = $localize`An error occured while saving new publication.`;
|
||||
console.error(errorMessage, error);
|
||||
this.#snackBar.open(errorMessage, $localize`Close`, {duration: 5000});
|
||||
})
|
||||
.finally(() => this.isSaving.set(false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Route } from "@angular/router";
|
||||
import { authenticationGuard } from "../../core/guard/authentication.guard";
|
||||
import { PublicationCreationComponent } from "./publication-creation.component";
|
||||
import {Route} from "@angular/router";
|
||||
import {authenticationGuard} from "../../core/guard/authentication.guard";
|
||||
import {PublicationCreationComponent} from "./publication-creation.component";
|
||||
|
||||
export const ROUTES: Route[] = [
|
||||
{ path: '', component: PublicationCreationComponent, canActivate: [authenticationGuard] }
|
||||
]
|
||||
{path: '', component: PublicationCreationComponent, canActivate: [authenticationGuard]}
|
||||
]
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
@if ((isLoading$ | async) == true) {
|
||||
<h2 i18n>Loading publication to edit...</h2>
|
||||
<mat-spinner></mat-spinner>
|
||||
}
|
||||
@else {
|
||||
@if (publication) {
|
||||
<app-publication-edition
|
||||
title="Update publication {{ publication.title }}"
|
||||
[publication]="publication"
|
||||
[isSaving$]="isSaving$"
|
||||
(publicationSave)="onPublicationSave($event)"
|
||||
i18n-title>
|
||||
</app-publication-edition>
|
||||
}
|
||||
@else {
|
||||
<div class="loading-failed">
|
||||
<h1 i18n>Publication failed to load...</h1>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@if (isLoading()) {
|
||||
<h2 i18n>Loading publication to edit...</h2>
|
||||
<mat-spinner/>
|
||||
} @else {
|
||||
@if (publication(); as publication) {
|
||||
<app-publication-edition
|
||||
title="Update publication {{ publication.title }}"
|
||||
[publication]="publication"
|
||||
[isSaving]="isSaving()"
|
||||
(publicationSave)="onPublicationSave($event)"
|
||||
i18n-title
|
||||
/>
|
||||
} @else {
|
||||
<div class="loading-failed">
|
||||
<h1 i18n>Publication failed to load...</h1>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
:host {
|
||||
display: flex;
|
||||
display: flex;
|
||||
|
||||
app-publication-edition {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
app-publication-edition {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +1,93 @@
|
||||
import { CommonModule, Location } from '@angular/common';
|
||||
import { Component, inject, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||
import { PublicationEditionComponent } from '../../components/publication-edition/publication-edition.component';
|
||||
import { Publication } from '../../core/rest-services/publications/model/publication';
|
||||
import { PublicationRestService } from '../../core/rest-services/publications/publication.rest-service';
|
||||
import {CommonModule, Location} from '@angular/common';
|
||||
import {Component, effect, inject, Signal, signal} from '@angular/core';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
import {MatDialogModule} from '@angular/material/dialog';
|
||||
import {MatIconModule} from '@angular/material/icon';
|
||||
import {MatInputModule} from '@angular/material/input';
|
||||
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
|
||||
import {MatSnackBar} from '@angular/material/snack-bar';
|
||||
import {MatTabsModule} from '@angular/material/tabs';
|
||||
import {MatTooltipModule} from '@angular/material/tooltip';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {PublicationEditionComponent} from '../../components/publication-edition/publication-edition.component';
|
||||
import {Publication} from '../../core/rest-services/publications/model/publication';
|
||||
import {PublicationRestService} from '../../core/rest-services/publications/publication.rest-service';
|
||||
import {toSignal} from "@angular/core/rxjs-interop";
|
||||
import {map} from "rxjs";
|
||||
|
||||
@Component({
|
||||
selector: 'app-publication-update',
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatDialogModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatTabsModule,
|
||||
MatTooltipModule,
|
||||
ReactiveFormsModule,
|
||||
PublicationEditionComponent
|
||||
],
|
||||
templateUrl: './publication-update.component.html',
|
||||
styleUrl: './publication-update.component.scss'
|
||||
selector: 'app-publication-update',
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatDialogModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatTabsModule,
|
||||
MatTooltipModule,
|
||||
ReactiveFormsModule,
|
||||
PublicationEditionComponent
|
||||
],
|
||||
templateUrl: './publication-update.component.html',
|
||||
styleUrl: './publication-update.component.scss'
|
||||
})
|
||||
export class PublicationUpdateComponent implements OnInit, OnDestroy {
|
||||
private readonly publicationRestService = inject(PublicationRestService);
|
||||
private readonly activatedRoute = inject(ActivatedRoute);
|
||||
private readonly location = inject(Location);
|
||||
private readonly router = inject(Router);
|
||||
private readonly snackBar = inject(MatSnackBar);
|
||||
private isLoadingSubject = new BehaviorSubject<boolean>(false);
|
||||
private isSavingSubject = new BehaviorSubject<boolean>(false);
|
||||
private subscriptions: Subscription[] = [];
|
||||
publication: Publication | undefined;
|
||||
export class PublicationUpdateComponent {
|
||||
readonly #activatedRoute = inject(ActivatedRoute);
|
||||
readonly #location = inject(Location);
|
||||
readonly #publicationRestService = inject(PublicationRestService);
|
||||
readonly #router = inject(Router);
|
||||
readonly #snackBar = inject(MatSnackBar);
|
||||
#isLoading = signal(false);
|
||||
#isSavingSubject = signal(false);
|
||||
publication = signal<Publication | undefined>(undefined);
|
||||
|
||||
get isLoading$(): Observable<boolean> {
|
||||
return this.isLoadingSubject.asObservable();
|
||||
#publicationId$ = this.#activatedRoute.params
|
||||
.pipe(map(queryParams => queryParams['publicationId']));
|
||||
#publicationId = toSignal(this.#publicationId$, {initialValue: undefined});
|
||||
|
||||
get isLoading(): Signal<boolean> {
|
||||
return this.#isLoading.asReadonly();
|
||||
}
|
||||
|
||||
get isSaving$(): Observable<boolean> {
|
||||
return this.isSavingSubject.asObservable();
|
||||
get isSaving(): Signal<boolean> {
|
||||
return this.#isSavingSubject.asReadonly();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.isLoadingSubject.next(true);
|
||||
const activatedRouteSubscription = this.activatedRoute.paramMap.subscribe(params => {
|
||||
const publicationId = params.get('publicationId');
|
||||
if (publicationId == undefined) {
|
||||
this.snackBar.open($localize`A technical error occurred while loading publication data.`, $localize`Close`, { duration: 5000 });
|
||||
this.location.back();
|
||||
} else {
|
||||
this.publicationRestService.getById(publicationId)
|
||||
.then(publication => {
|
||||
this.publication = publication;
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = $localize`A technical error occurred while loading publication data.`;
|
||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||
console.error(errorMessage, error)
|
||||
})
|
||||
.finally(() => this.isLoadingSubject.next(false));
|
||||
}
|
||||
});
|
||||
this.subscriptions.push(activatedRouteSubscription);
|
||||
}
|
||||
constructor() {
|
||||
effect(() => {
|
||||
this.#isLoading.set(true);
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subscriptions.forEach(subscription => subscription?.unsubscribe());
|
||||
const publicationId = this.#publicationId();
|
||||
if (publicationId) {
|
||||
this.#publicationRestService.getById(publicationId)
|
||||
.then(publication => {
|
||||
this.publication.set(publication);
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = $localize`A technical error occurred while loading publication data.`;
|
||||
this.#snackBar.open(errorMessage, $localize`Close`, {duration: 5000});
|
||||
console.error(errorMessage, error)
|
||||
})
|
||||
.finally(() => this.#isLoading.set(false));
|
||||
} else {
|
||||
this.#snackBar.open($localize`A technical error occurred while loading publication data.`, $localize`Close`, {duration: 5000});
|
||||
this.#location.back();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onPublicationSave(publication: Publication): void {
|
||||
this.isSavingSubject.next(true);
|
||||
this.publicationRestService.update(publication)
|
||||
.then(() => {
|
||||
this.snackBar.open($localize`Publication updated succesfully!`, $localize`Close`, { duration: 5000 });
|
||||
this.router.navigate(['/home']);
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = $localize`An error occured while saving publication modifications.`;
|
||||
console.error(errorMessage, error);
|
||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||
})
|
||||
.finally(() => this.isSavingSubject.next(false));
|
||||
this.#isSavingSubject.set(true);
|
||||
this.#publicationRestService.update(publication)
|
||||
.then(() => {
|
||||
this.#snackBar.open($localize`Publication updated succesfully!`, $localize`Close`, {duration: 5000});
|
||||
this.#router.navigate(['/home']);
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = $localize`An error occured while saving publication modifications.`;
|
||||
console.error(errorMessage, error);
|
||||
this.#snackBar.open(errorMessage, $localize`Close`, {duration: 5000});
|
||||
})
|
||||
.finally(() => this.#isSavingSubject.set(false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Route } from "@angular/router";
|
||||
import { PublicationUpdateComponent } from "./publication-update.component";
|
||||
import { authenticationGuard } from "../../core/guard/authentication.guard";
|
||||
import {Route} from "@angular/router";
|
||||
import {PublicationUpdateComponent} from "./publication-update.component";
|
||||
import {authenticationGuard} from "../../core/guard/authentication.guard";
|
||||
|
||||
export const ROUTES: Route[] = [
|
||||
{ path: '', component: PublicationUpdateComponent, canActivate: [authenticationGuard] }
|
||||
]
|
||||
{path: '', component: PublicationUpdateComponent, canActivate: [authenticationGuard]}
|
||||
]
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
@if (isLoading) {
|
||||
<h2 i18n>Publication content loading...</h2>
|
||||
<mat-spinner></mat-spinner>
|
||||
@if (isLoading()) {
|
||||
<h2 i18n>Publication content loading...</h2>
|
||||
<mat-spinner></mat-spinner>
|
||||
} @else {
|
||||
@if (publication) {
|
||||
<div class="card">
|
||||
<img src="/api/pictures/{{ publication.illustrationId }}" />
|
||||
<header>
|
||||
<h1>{{ publication.title }}</h1>
|
||||
<h2>{{ publication.description }}</h2>
|
||||
@if (isAuthorAndUserEquals) {
|
||||
<a [routerLink]="['edit']"
|
||||
class="button action"
|
||||
matTooltip="Click to edit the publication"
|
||||
matRipple
|
||||
i18n-matTooltip>
|
||||
<mat-icon>edit</mat-icon>
|
||||
</a>
|
||||
}
|
||||
</header>
|
||||
<main [innerHTML]="publication.parsedText"></main>
|
||||
<footer>
|
||||
<div class="metadata">
|
||||
<img src="/api/pictures/{{ publication.author.image }}" [matTooltip]="publication.author.name" />
|
||||
<div class="posting-data">
|
||||
<span i18n>Publication posted by {{ publication.author.name }}</span>
|
||||
<span class="publication-date">
|
||||
@if (publication(); as publication) {
|
||||
<div class="card">
|
||||
<img src="/api/pictures/{{ publication.illustrationId }}"/>
|
||||
<header>
|
||||
<h1>{{ publication.title }}</h1>
|
||||
<h2>{{ publication.description }}</h2>
|
||||
@if (isAuthorAndUserEquals()) {
|
||||
<a [routerLink]="['edit']"
|
||||
class="button action"
|
||||
matTooltip="Click to edit the publication"
|
||||
matRipple
|
||||
i18n-matTooltip>
|
||||
<mat-icon>edit</mat-icon>
|
||||
</a>
|
||||
}
|
||||
</header>
|
||||
<main [innerHTML]="publication.parsedText"></main>
|
||||
<footer>
|
||||
<div class="metadata">
|
||||
<img src="/api/pictures/{{ publication.author.image }}" [matTooltip]="publication.author.name"/>
|
||||
<div class="posting-data">
|
||||
<span i18n>Publication posted by {{ publication.author.name }}</span>
|
||||
<span class="publication-date">
|
||||
({{ publication.creationDate | date: 'short' }})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@if (isAuthorAndUserEquals) {
|
||||
<button type="button"
|
||||
(click)="deletePublication()"
|
||||
matTooltip="Click to delete the publication"
|
||||
matTooltipPosition="left"
|
||||
matRipple
|
||||
i18n-matTooltip>
|
||||
<mat-icon>delete</mat-icon>
|
||||
Delete
|
||||
</button>
|
||||
}
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="loading-failed">
|
||||
<h1 i18n>Publication failed to load...</h1>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@if (isAuthorAndUserEquals()) {
|
||||
<button type="button"
|
||||
(click)="deletePublication()"
|
||||
matTooltip="Click to delete the publication"
|
||||
matTooltipPosition="left"
|
||||
matRipple
|
||||
i18n-matTooltip>
|
||||
<mat-icon>delete</mat-icon>
|
||||
Delete
|
||||
</button>
|
||||
}
|
||||
</footer>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="loading-failed">
|
||||
<h1 i18n>Publication failed to load...</h1>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,140 +1,140 @@
|
||||
$cardBorderRadius: .5em;
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.card {
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 1em;
|
||||
max-width: 80em;
|
||||
width: 90%;
|
||||
border-radius: .5em;
|
||||
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .16), 0 2px 10px 0 rgba(0, 0, 0, .12);
|
||||
|
||||
img {
|
||||
height: 12em;
|
||||
object-fit: cover;
|
||||
border-radius: $cardBorderRadius $cardBorderRadius 0 0;
|
||||
transition: height .2s ease-in-out;
|
||||
|
||||
@media screen and (min-width: 450px) {
|
||||
height: 15em;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
height: 20em;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 750px) {
|
||||
height: 25em;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 2em;
|
||||
position: relative;
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.2em;
|
||||
line-height: 1.6em;
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
a {
|
||||
padding: .8em .8em;
|
||||
border-radius: 10em;
|
||||
border: none;
|
||||
background-color: #3f51b5;
|
||||
color: white;
|
||||
transition: background-color .2s ease-in-out;
|
||||
position: absolute;
|
||||
top: -1.5em;
|
||||
right: 3em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 1em;
|
||||
max-width: 80em;
|
||||
width: 90%;
|
||||
border-radius: .5em;
|
||||
box-shadow: 0 2px 5px 0 rgba(0,0,0,.16),0 2px 10px 0 rgba(0,0,0,.12);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
background-color: #5b6ed8;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
border-top: 1px solid #dddddd;
|
||||
margin: 0 2em 2em 2em;
|
||||
padding-top: 2em;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 0 0 $cardBorderRadius $cardBorderRadius;
|
||||
padding: 1em 2em;
|
||||
gap: 1em;
|
||||
|
||||
@media screen and (min-width: 500px) {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.metadata {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 1em;
|
||||
|
||||
img {
|
||||
height: 12em;
|
||||
object-fit: cover;
|
||||
border-radius: $cardBorderRadius $cardBorderRadius 0 0;
|
||||
transition: height .2s ease-in-out;
|
||||
|
||||
@media screen and (min-width: 450px) {
|
||||
height: 15em;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
height: 20em;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 750px) {
|
||||
height: 25em;
|
||||
}
|
||||
$imageSize: 4em;
|
||||
border-radius: 10em;
|
||||
width: $imageSize;
|
||||
height: $imageSize;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 2em;
|
||||
position: relative;
|
||||
.posting-data {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: start;
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.2em;
|
||||
line-height: 1.6em;
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
a {
|
||||
padding: .8em .8em;
|
||||
border-radius: 10em;
|
||||
border: none;
|
||||
background-color: #3f51b5;
|
||||
color: white;
|
||||
transition: background-color .2s ease-in-out;
|
||||
position: absolute;
|
||||
top: -1.5em;
|
||||
right: 3em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
background-color: #5b6ed8;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.publication-date {
|
||||
font-style: italic;
|
||||
color: #bdbdbd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
border-top: 1px solid #dddddd;
|
||||
margin: 0 2em 2em 2em;
|
||||
padding-top: 2em;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 0 0 $cardBorderRadius $cardBorderRadius;
|
||||
padding: 1em 2em;
|
||||
gap: 1em;
|
||||
|
||||
@media screen and (min-width: 500px) {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.metadata {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 1em;
|
||||
|
||||
img {
|
||||
$imageSize: 4em;
|
||||
border-radius: 10em;
|
||||
width: $imageSize;
|
||||
height: $imageSize;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.posting-data {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: start;
|
||||
|
||||
.publication-date {
|
||||
font-style: italic;
|
||||
color: #bdbdbd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
padding: .8em 1.2em;
|
||||
border-radius: 10em;
|
||||
border: none;
|
||||
background-color: #D50000;
|
||||
color: white;
|
||||
transition: background-color .2s ease-in-out;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: .5em;
|
||||
|
||||
&:hover {
|
||||
background-color: #E53935;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
button {
|
||||
padding: .8em 1.2em;
|
||||
border-radius: 10em;
|
||||
border: none;
|
||||
background-color: #D50000;
|
||||
color: white;
|
||||
transition: background-color .2s ease-in-out;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: .5em;
|
||||
|
||||
&:hover {
|
||||
background-color: #E53935;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
import { CommonModule, Location } from '@angular/common';
|
||||
import { Component, OnDestroy, OnInit, inject } from '@angular/core';
|
||||
import { MatRippleModule } from '@angular/material/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { ActivatedRoute, RouterModule } from '@angular/router';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ConfirmationDialog } from '../../components/confirmation-dialog/confirmation-dialog.component';
|
||||
import { Publication } from '../../core/rest-services/publications/model/publication';
|
||||
import { PublicationRestService } from '../../core/rest-services/publications/publication.rest-service';
|
||||
import { AuthenticationService } from '../../core/service/authentication.service';
|
||||
import {CommonModule, Location} from '@angular/common';
|
||||
import {Component, inject, OnDestroy, OnInit, signal} from '@angular/core';
|
||||
import {MatRippleModule} from '@angular/material/core';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {MatProgressSpinner} from '@angular/material/progress-spinner';
|
||||
import {MatSnackBar} from '@angular/material/snack-bar';
|
||||
import {MatTooltipModule} from '@angular/material/tooltip';
|
||||
import {ActivatedRoute, RouterModule} from '@angular/router';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {ConfirmationDialog} from '../../components/confirmation-dialog/confirmation-dialog.component';
|
||||
import {Publication} from '../../core/rest-services/publications/model/publication';
|
||||
import {PublicationRestService} from '../../core/rest-services/publications/publication.rest-service';
|
||||
import {AuthenticationService} from '../../core/service/authentication.service';
|
||||
|
||||
declare let Prism: any;
|
||||
|
||||
@Component({
|
||||
selector: 'app-publication',
|
||||
templateUrl: './publication.component.html',
|
||||
styleUrl: './publication.component.scss',
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatIcon,
|
||||
MatRippleModule,
|
||||
MatProgressSpinner,
|
||||
MatTooltipModule,
|
||||
RouterModule
|
||||
]
|
||||
selector: 'app-publication',
|
||||
templateUrl: './publication.component.html',
|
||||
styleUrl: './publication.component.scss',
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatIcon,
|
||||
MatRippleModule,
|
||||
MatProgressSpinner,
|
||||
MatTooltipModule,
|
||||
RouterModule
|
||||
]
|
||||
})
|
||||
export class PublicationComponent implements OnInit, OnDestroy {
|
||||
private readonly activatedRoute = inject(ActivatedRoute);
|
||||
@@ -37,35 +37,35 @@ export class PublicationComponent implements OnInit, OnDestroy {
|
||||
private readonly snackBar = inject(MatSnackBar);
|
||||
private paramMapSubscription?: Subscription;
|
||||
private afterDialogCloseSubscription?: Subscription;
|
||||
isLoading: boolean = false;
|
||||
isAuthorAndUserEquals: boolean = false;
|
||||
publication?: Publication;
|
||||
isLoading = signal(false);
|
||||
isAuthorAndUserEquals = signal(false);
|
||||
publication = signal<Publication | null>(null);
|
||||
|
||||
ngOnInit(): void {
|
||||
this.paramMapSubscription = this.activatedRoute
|
||||
.paramMap
|
||||
.subscribe(params => {
|
||||
const publicationId = params.get('publicationId');
|
||||
.paramMap
|
||||
.subscribe(params => {
|
||||
const publicationId = params.get('publicationId');
|
||||
|
||||
if (publicationId) {
|
||||
this.isLoading = true;
|
||||
if (publicationId) {
|
||||
this.isLoading.set(true);
|
||||
|
||||
this.publicationRestService.getById(publicationId)
|
||||
.then(publication => {
|
||||
this.publication = publication;
|
||||
this.isAuthorAndUserEquals = this.authenticationService.getAuthenticatedUser()?.id === this.publication.author.id;
|
||||
setTimeout(() => Prism.highlightAll(), 100);
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = $localize`An error occurred while loading publication...`;
|
||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||
console.error(errorMessage, error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
this.publicationRestService.getById(publicationId)
|
||||
.then(publication => {
|
||||
this.publication.set(publication);
|
||||
this.isAuthorAndUserEquals.set(this.authenticationService.getAuthenticatedUser()?.id === this.publication()?.author.id);
|
||||
setTimeout(() => Prism.highlightAll(), 100);
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = $localize`An error occurred while loading publication...`;
|
||||
this.snackBar.open(errorMessage, $localize`Close`, {duration: 5000});
|
||||
console.error(errorMessage, error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading.set(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@@ -86,9 +86,10 @@ export class PublicationComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.afterDialogCloseSubscription = dialogRef.afterClosed()
|
||||
.subscribe(response => {
|
||||
if (response && this.publication?.id) {
|
||||
this.publicationRestService.delete(this.publication.id);
|
||||
this.snackBar.open($localize`Publication deleted`, $localize`Close`, { duration: 5000 });
|
||||
const publication = this.publication();
|
||||
if (response && publication?.id) {
|
||||
this.publicationRestService.delete(publication.id);
|
||||
this.snackBar.open($localize`Publication deleted`, $localize`Close`, {duration: 5000});
|
||||
this.location.back();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<h1 i18n>Search results</h1>
|
||||
@if((isLoading$ | async) === true) {
|
||||
<h2 i18n>Search in progress...</h2>
|
||||
<mat-spinner></mat-spinner>
|
||||
} @else if((isLoaded$ | async) === true) {
|
||||
@if((publications$ | async)?.length) {
|
||||
<app-publication-list [publications$]="publications$"></app-publication-list>
|
||||
} @else {
|
||||
<span i18n>No any result.</span>
|
||||
}
|
||||
} @else {
|
||||
@if (isLoading()) {
|
||||
<h2 i18n>Search in progress...</h2>
|
||||
<mat-spinner/>
|
||||
} @else if (isLoaded()) {
|
||||
@if (publications().length) {
|
||||
<app-publication-list [publications]="publications()"/>
|
||||
} @else {
|
||||
<span i18n>No any result.</span>
|
||||
}
|
||||
} @else {
|
||||
<span i18n>No any result.</span>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -1,51 +1,39 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, inject, OnDestroy, OnInit } from "@angular/core";
|
||||
import { MatProgressSpinner } from "@angular/material/progress-spinner";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { Observable, Subscription } from "rxjs";
|
||||
import { PublicationListComponent } from "../../components/publication-list/publication-list.component";
|
||||
import { Publication } from "../../core/rest-services/publications/model/publication";
|
||||
import { SearchPublicationsService } from "./search-publications.service";
|
||||
import {CommonModule} from "@angular/common";
|
||||
import {Component, effect, inject} from "@angular/core";
|
||||
import {MatProgressSpinner} from "@angular/material/progress-spinner";
|
||||
import {ActivatedRoute} from "@angular/router";
|
||||
import {PublicationListComponent} from "../../components/publication-list/publication-list.component";
|
||||
import {SearchPublicationsService} from "./search-publications.service";
|
||||
import {toSignal} from "@angular/core/rxjs-interop";
|
||||
|
||||
@Component({
|
||||
selector: 'app-search-publications',
|
||||
templateUrl: './search-publications.component.html',
|
||||
styleUrl: './search-publications.component.scss',
|
||||
imports: [CommonModule, MatProgressSpinner, PublicationListComponent],
|
||||
providers: [SearchPublicationsService]
|
||||
selector: 'app-search-publications',
|
||||
templateUrl: './search-publications.component.html',
|
||||
styleUrl: './search-publications.component.scss',
|
||||
imports: [CommonModule, MatProgressSpinner, PublicationListComponent],
|
||||
providers: [SearchPublicationsService]
|
||||
})
|
||||
export class SearchPublicationsComponent implements OnInit, OnDestroy {
|
||||
private searchPublicationsService = inject(SearchPublicationsService);
|
||||
private activatedRoute = inject(ActivatedRoute);
|
||||
private queryParamsSubscription?: Subscription;
|
||||
export class SearchPublicationsComponent {
|
||||
private searchPublicationsService = inject(SearchPublicationsService);
|
||||
private activatedRoute = inject(ActivatedRoute);
|
||||
publications = this.searchPublicationsService.publications;
|
||||
isLoading = this.searchPublicationsService.isLoading;
|
||||
isLoaded = this.searchPublicationsService.isLoaded;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.queryParamsSubscription = this.activatedRoute.queryParamMap
|
||||
.subscribe(params => {
|
||||
const categoryId = params.get('category-id');
|
||||
if (categoryId) {
|
||||
this.searchPublicationsService.loadPublications(`category_id=${categoryId}`);
|
||||
}
|
||||
const query = params.get('query')
|
||||
if (query) {
|
||||
this.searchPublicationsService.loadPublications(query);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.queryParamsSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
get publications$(): Observable<Publication[]> {
|
||||
return this.searchPublicationsService.publications$;
|
||||
}
|
||||
|
||||
get isLoading$(): Observable<boolean> {
|
||||
return this.searchPublicationsService.isLoading$;
|
||||
}
|
||||
|
||||
get isLoaded$(): Observable<boolean> {
|
||||
return this.searchPublicationsService.isLoaded$;
|
||||
}
|
||||
}
|
||||
constructor() {
|
||||
const queryParams = toSignal(this.activatedRoute.queryParamMap);
|
||||
effect(() => {
|
||||
let params = queryParams();
|
||||
if (params) {
|
||||
const categoryId = params.get('category-id');
|
||||
if (categoryId) {
|
||||
this.searchPublicationsService.loadPublications(`category_id=${categoryId}`);
|
||||
}
|
||||
const query = params.get('query')
|
||||
if (query) {
|
||||
this.searchPublicationsService.loadPublications(query);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,47 @@
|
||||
import { inject, Injectable } from "@angular/core";
|
||||
import { PublicationRestService } from "../../core/rest-services/publications/publication.rest-service";
|
||||
import { BehaviorSubject, Observable } from "rxjs";
|
||||
import { Publication } from "../../core/rest-services/publications/model/publication";
|
||||
import { MatSnackBar } from "@angular/material/snack-bar";
|
||||
|
||||
import {inject, Injectable, Signal, signal} from "@angular/core";
|
||||
import {PublicationRestService} from "../../core/rest-services/publications/publication.rest-service";
|
||||
import {Publication} from "../../core/rest-services/publications/model/publication";
|
||||
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||
|
||||
@Injectable()
|
||||
export class SearchPublicationsService {
|
||||
private publicationRestService = inject(PublicationRestService);
|
||||
private publicationsSubject = new BehaviorSubject<Publication[]>([]);
|
||||
private isLoadingSubject = new BehaviorSubject<boolean>(false);
|
||||
private isLoadedSubject = new BehaviorSubject<boolean>(false);
|
||||
private snackBar = inject(MatSnackBar);
|
||||
#snackBar = inject(MatSnackBar);
|
||||
#publicationRestService = inject(PublicationRestService);
|
||||
#publications = signal<Publication[]>([]);
|
||||
#isLoading = signal(false);
|
||||
#isLoaded = signal(false);
|
||||
|
||||
get publications$(): Observable<Publication[]> {
|
||||
return this.publicationsSubject.asObservable();
|
||||
}
|
||||
|
||||
get isLoading$(): Observable<boolean> {
|
||||
return this.isLoadingSubject.asObservable();
|
||||
}
|
||||
get publications(): Signal<Publication[]> {
|
||||
return this.#publications.asReadonly();
|
||||
}
|
||||
|
||||
get isLoaded$(): Observable<boolean> {
|
||||
return this.isLoadedSubject.asObservable();
|
||||
}
|
||||
get isLoading(): Signal<boolean> {
|
||||
return this.#isLoading.asReadonly();
|
||||
}
|
||||
|
||||
loadPublications(searchCriteria: string): void {
|
||||
this.isLoadingSubject.next(true);
|
||||
this.isLoadedSubject.next(false);
|
||||
this.publicationsSubject.next([]);
|
||||
get isLoaded(): Signal<boolean> {
|
||||
return this.#isLoaded.asReadonly();
|
||||
}
|
||||
|
||||
this.publicationRestService.search(searchCriteria)
|
||||
.then(publications => {
|
||||
this.publicationsSubject.next(publications);
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.status !== 404) {
|
||||
const errorMessage = $localize`An error occured while retrieving publications.`;
|
||||
console.error(errorMessage, error);
|
||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoadingSubject.next(false);
|
||||
this.isLoadedSubject.next(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
loadPublications(searchCriteria: string): void {
|
||||
this.#isLoading.set(true);
|
||||
this.#isLoaded.set(false);
|
||||
this.#publications.set([]);
|
||||
|
||||
this.#publicationRestService.search(searchCriteria)
|
||||
.then(publications => {
|
||||
this.#publications.set(publications);
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.status !== 404) {
|
||||
const errorMessage = $localize`An error occured while retrieving publications.`;
|
||||
console.error(errorMessage, error);
|
||||
this.#snackBar.open(errorMessage, $localize`Close`, {duration: 5000});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.#isLoading.set(false);
|
||||
this.#isLoaded.set(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
<form [formGroup]="signinForm" (submit)="performSignin()" class="cod-form card" ngNativeValidate>
|
||||
<h1 i18n>Signin</h1>
|
||||
<div class="form-field">
|
||||
<mat-icon>person</mat-icon>
|
||||
<label for="pseudo" i18n>
|
||||
Pseudo
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
<input type="text" id="pseudo" formControlName="pseudo" autocomplete="pseudo" required />
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<mat-icon>mail</mat-icon>
|
||||
<label for="email" i18n>
|
||||
Email address
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
<input type="email" id="email" formControlName="email" autocomplete="email" required />
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<mat-icon>lock</mat-icon>
|
||||
<label for="password" i18n>
|
||||
Password
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
<input type="password" id="password" formControlName="password" required />
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<mat-icon>lock</mat-icon>
|
||||
<label for="confirm-password" i18n>
|
||||
Confirm password
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
<input type="password" id="confirm-password" formControlName="confirmPassword" required />
|
||||
</div>
|
||||
<div class="actions reversed">
|
||||
<app-submit-button [requestPending]="false" [disabled]="false" i18n>Send</app-submit-button>
|
||||
<a [routerLink]="['/login']" class="cod-button secondary" matRipple i18n>I already have an account</a>
|
||||
</div>
|
||||
</form>
|
||||
<h1 i18n>Signin</h1>
|
||||
<div class="form-field">
|
||||
<mat-icon>person</mat-icon>
|
||||
<label for="pseudo" i18n>
|
||||
Pseudo
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
<input type="text" id="pseudo" formControlName="pseudo" autocomplete="pseudo" required/>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<mat-icon>mail</mat-icon>
|
||||
<label for="email" i18n>
|
||||
Email address
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
<input type="email" id="email" formControlName="email" autocomplete="email" required/>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<mat-icon>lock</mat-icon>
|
||||
<label for="password" i18n>
|
||||
Password
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
<input type="password" id="password" formControlName="password" required/>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<mat-icon>lock</mat-icon>
|
||||
<label for="confirm-password" i18n>
|
||||
Confirm password
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
<input type="password" id="confirm-password" formControlName="confirmPassword" required/>
|
||||
</div>
|
||||
<div class="actions reversed">
|
||||
<app-submit-button [requestPending]="false" [disabled]="false" i18n>Send</app-submit-button>
|
||||
<a [routerLink]="['/login']" class="cod-button secondary" matRipple i18n>I already have an account</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1em;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import { Component, OnDestroy, OnInit, inject } from '@angular/core';
|
||||
import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { Subscription, debounceTime, distinctUntilChanged, map } from 'rxjs';
|
||||
import { SigninService } from './signin.service';
|
||||
import { LoginService } from '../login/login.service';
|
||||
import { FormError } from '../../core/model/FormError';
|
||||
import { SubmitButtonComponent } from "../../components/submit-button/submit-button.component";
|
||||
import { MatRippleModule } from '@angular/material/core';
|
||||
import {Component, inject, OnDestroy, OnInit} from '@angular/core';
|
||||
import {FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||
import {MatIconModule} from '@angular/material/icon';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {debounceTime, distinctUntilChanged, map, Subscription} from 'rxjs';
|
||||
import {SigninService} from './signin.service';
|
||||
import {LoginService} from '../login/login.service';
|
||||
import {FormError} from '../../core/model/FormError';
|
||||
import {SubmitButtonComponent} from "../../components/submit-button/submit-button.component";
|
||||
import {MatRippleModule} from '@angular/material/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-signin',
|
||||
templateUrl: './signin.component.html',
|
||||
styleUrl: './signin.component.scss',
|
||||
imports: [
|
||||
MatIconModule,
|
||||
MatRippleModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule,
|
||||
SubmitButtonComponent
|
||||
],
|
||||
providers: [SigninService, LoginService]
|
||||
selector: 'app-signin',
|
||||
templateUrl: './signin.component.html',
|
||||
styleUrl: './signin.component.scss',
|
||||
imports: [
|
||||
MatIconModule,
|
||||
MatRippleModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule,
|
||||
SubmitButtonComponent
|
||||
],
|
||||
providers: [SigninService, LoginService]
|
||||
})
|
||||
export class SigninComponent implements OnInit, OnDestroy {
|
||||
private signinService = inject(SigninService);
|
||||
@@ -38,63 +38,63 @@ export class SigninComponent implements OnInit, OnDestroy {
|
||||
|
||||
ngOnInit(): void {
|
||||
const pseudoSubscription = this.signinForm.controls['pseudo'].valueChanges
|
||||
.pipe(
|
||||
debounceTime(300),
|
||||
distinctUntilChanged(),
|
||||
map(value => value?.length ? value as string : '')
|
||||
)
|
||||
.subscribe(pseudo => {
|
||||
this.signinService.editPseudo(pseudo);
|
||||
});
|
||||
.pipe(
|
||||
debounceTime(300),
|
||||
distinctUntilChanged(),
|
||||
map(value => value?.length ? value as string : '')
|
||||
)
|
||||
.subscribe(pseudo => {
|
||||
this.signinService.editPseudo(pseudo);
|
||||
});
|
||||
this.subscriptions.push(pseudoSubscription);
|
||||
|
||||
const emailSubscription = this.signinForm.controls['email'].valueChanges
|
||||
.pipe(
|
||||
debounceTime(300),
|
||||
distinctUntilChanged(),
|
||||
map(value => value?.length ? value as string : '')
|
||||
)
|
||||
.subscribe(email => {
|
||||
this.signinService.editEmail(email);
|
||||
});
|
||||
.pipe(
|
||||
debounceTime(300),
|
||||
distinctUntilChanged(),
|
||||
map(value => value?.length ? value as string : '')
|
||||
)
|
||||
.subscribe(email => {
|
||||
this.signinService.editEmail(email);
|
||||
});
|
||||
this.subscriptions.push(emailSubscription);
|
||||
|
||||
const passwordSubscription = this.signinForm.controls['password'].valueChanges
|
||||
.pipe(
|
||||
debounceTime(300),
|
||||
distinctUntilChanged(),
|
||||
map(value => value?.length ? value as string : '')
|
||||
)
|
||||
.subscribe(password => {
|
||||
this.signinService.editPassword(password);
|
||||
});
|
||||
.pipe(
|
||||
debounceTime(300),
|
||||
distinctUntilChanged(),
|
||||
map(value => value?.length ? value as string : '')
|
||||
)
|
||||
.subscribe(password => {
|
||||
this.signinService.editPassword(password);
|
||||
});
|
||||
this.subscriptions.push(passwordSubscription);
|
||||
|
||||
const confirmPasswordSubscription = this.signinForm.controls['confirmPassword'].valueChanges
|
||||
.pipe(
|
||||
debounceTime(300),
|
||||
distinctUntilChanged(),
|
||||
map(value => value?.length ? value as string : '')
|
||||
)
|
||||
.subscribe(confirmPassword => {
|
||||
this.signinService.editConfirmPassword(confirmPassword);
|
||||
});
|
||||
.pipe(
|
||||
debounceTime(300),
|
||||
distinctUntilChanged(),
|
||||
map(value => value?.length ? value as string : '')
|
||||
)
|
||||
.subscribe(confirmPassword => {
|
||||
this.signinService.editConfirmPassword(confirmPassword);
|
||||
});
|
||||
this.subscriptions.push(confirmPasswordSubscription);
|
||||
|
||||
const stateSubscription = this.signinService.state$
|
||||
.subscribe(state => {
|
||||
this.signinForm.controls['pseudo'].setValue(state.request.pseudo, { emitEvent: false });
|
||||
this.signinForm.controls['email'].setValue(state.request.email, { emitEvent: false });
|
||||
this.signinForm.controls['password'].setValue(state.request.password, { emitEvent: false });
|
||||
this.signinForm.controls['confirmPassword'].setValue(state.confirmPassword, { emitEvent: false });
|
||||
});
|
||||
.subscribe(state => {
|
||||
this.signinForm.controls['pseudo'].setValue(state.request.pseudo, {emitEvent: false});
|
||||
this.signinForm.controls['email'].setValue(state.request.email, {emitEvent: false});
|
||||
this.signinForm.controls['password'].setValue(state.request.password, {emitEvent: false});
|
||||
this.signinForm.controls['confirmPassword'].setValue(state.confirmPassword, {emitEvent: false});
|
||||
});
|
||||
this.subscriptions.push(stateSubscription);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subscriptions.forEach(subscription => subscription.unsubscribe());
|
||||
}
|
||||
|
||||
|
||||
performSignin(): void {
|
||||
if (this.signinForm.valid) {
|
||||
this.signinService.performSignin();
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { SigninRequest } from '../../core/rest-services/user/model/signin.model';
|
||||
import { FormError } from '../../core/model/FormError';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { copy } from '../../core/utils/ObjectUtils';
|
||||
import { UserRestService } from '../../core/rest-services/user/user.rest-service';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Router } from '@angular/router';
|
||||
import { LoginService } from '../login/login.service';
|
||||
import {inject, Injectable} from '@angular/core';
|
||||
import {SigninRequest} from '../../core/rest-services/user/model/signin.model';
|
||||
import {FormError} from '../../core/model/FormError';
|
||||
import {BehaviorSubject, Observable} from 'rxjs';
|
||||
import {copy} from '../../core/utils/ObjectUtils';
|
||||
import {UserRestService} from '../../core/rest-services/user/user.rest-service';
|
||||
import {MatSnackBar} from '@angular/material/snack-bar';
|
||||
import {Router} from '@angular/router';
|
||||
import {LoginService} from '../login/login.service';
|
||||
|
||||
export interface SigninState {
|
||||
request: SigninRequest;
|
||||
@@ -74,12 +74,12 @@ export class SigninService {
|
||||
state.confirmPassword = newConfirmPassword;
|
||||
|
||||
if (state.request.password !== state.confirmPassword) {
|
||||
const confirmPasswordError: FormError = {
|
||||
fieldName: 'confirmPassword',
|
||||
errorMessage: $localize`Typed passwords are different.`
|
||||
}
|
||||
state.errors.filter(error => error.fieldName !== 'confirmPassword');
|
||||
state.errors.push(confirmPasswordError)
|
||||
const confirmPasswordError: FormError = {
|
||||
fieldName: 'confirmPassword',
|
||||
errorMessage: $localize`Typed passwords are different.`
|
||||
}
|
||||
state.errors.filter(error => error.fieldName !== 'confirmPassword');
|
||||
state.errors.push(confirmPasswordError)
|
||||
}
|
||||
|
||||
this.save(state);
|
||||
@@ -91,12 +91,12 @@ export class SigninService {
|
||||
// Check state is valid
|
||||
|
||||
this.userRestService
|
||||
.signin(state.request)
|
||||
.then(() => {
|
||||
this.loginService.editEmail(state.request.email!!);
|
||||
this.loginService.editPassword(state.request.password!!);
|
||||
.signin(state.request)
|
||||
.then(() => {
|
||||
this.loginService.editEmail(state.request.email!!);
|
||||
this.loginService.editPassword(state.request.password!!);
|
||||
|
||||
this.loginService.performLogin();
|
||||
})
|
||||
this.loginService.performLogin();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user