Compare commits
6 Commits
882ffe7094
...
signals-co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4f887bf33 | ||
|
|
61ec781bbb | ||
|
|
3ee41cf571 | ||
|
|
7f2e762a44 | ||
|
|
cfa3015a7b | ||
|
|
778a92fd5e |
@@ -1,11 +1,10 @@
|
|||||||
import { APP_INITIALIZER, ApplicationConfig } from '@angular/core';
|
import { ApplicationConfig, provideExperimentalZonelessChangeDetection } from '@angular/core';
|
||||||
import { provideRouter, withRouterConfig } from '@angular/router';
|
import { provideRouter, withRouterConfig } from '@angular/router';
|
||||||
|
|
||||||
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
||||||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
import { JwtInterceptor } from './core/interceptor/jwt.interceptor';
|
import { JwtInterceptor } from './core/interceptor/jwt.interceptor';
|
||||||
import { AuthenticationService } from './core/service/authentication.service';
|
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
@@ -17,13 +16,9 @@ export const appConfig: ApplicationConfig = {
|
|||||||
})
|
})
|
||||||
),
|
),
|
||||||
provideAnimationsAsync(),
|
provideAnimationsAsync(),
|
||||||
|
provideExperimentalZonelessChangeDetection(),
|
||||||
provideHttpClient(withInterceptorsFromDi()),
|
provideHttpClient(withInterceptorsFromDi()),
|
||||||
|
|
||||||
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
|
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
|
||||||
{
|
|
||||||
provide: APP_INITIALIZER,
|
|
||||||
useFactory: (authenticationService: AuthenticationService) => () => authenticationService.startAuthenticationCheckingProcess(),
|
|
||||||
deps: [AuthenticationService],
|
|
||||||
multi: true
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
import { alreadyAuthenticatedGuard } from './core/guard/already-authenticated.guard';
|
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'login',
|
path: 'login',
|
||||||
loadComponent: () => import('./pages/login/login.component').then(module => module.LoginComponent),
|
loadComponent: () => import('./pages/login/login.component').then(module => module.LoginComponent)
|
||||||
canActivate: [alreadyAuthenticatedGuard]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'signin',
|
path: 'signin',
|
||||||
loadComponent: () => import('./pages/signin/signin.component').then(module => module.SigninComponent),
|
loadComponent: () => import('./pages/signin/signin.component').then(module => module.SigninComponent)
|
||||||
canActivate: [alreadyAuthenticatedGuard]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'disconnect',
|
path: 'disconnect',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@for(category of categories$ | async; track category) {
|
@for(category of categories(); track category) {
|
||||||
<div class="category {{category.isOpenned ? 'openned' : ''}}">
|
<div class="category {{category.isOpenned ? 'openned' : ''}}">
|
||||||
<div id="category-{{category.id}}" class="category-header" (click)="setOpenned(category)">
|
<div id="category-{{category.id}}" class="category-header" (click)="setOpenned(category)">
|
||||||
{{category.name}}
|
{{category.name}}
|
||||||
<mat-icon>chevron_right</mat-icon>
|
<mat-icon>chevron_right</mat-icon>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { Component, EventEmitter, inject, OnInit, Output } from "@angular/core";
|
import { Component, EventEmitter, inject, OnDestroy, OnInit, Output, signal } from "@angular/core";
|
||||||
import { MatIconModule } from "@angular/material/icon";
|
import { MatIconModule } from "@angular/material/icon";
|
||||||
import { DisplayableCategory, SideMenuService } from "../side-menu.service";
|
import { DisplayableCategory, SideMenuService } from "../side-menu.service";
|
||||||
import { Observable } from "rxjs";
|
import { Observable, Subscription } from "rxjs";
|
||||||
import { RouterModule } from "@angular/router";
|
import { RouterModule } from "@angular/router";
|
||||||
|
import { Category } from "../../../core/rest-services/category/model/category";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-categories-menu',
|
selector: 'app-categories-menu',
|
||||||
@@ -12,17 +13,21 @@ import { RouterModule } from "@angular/router";
|
|||||||
templateUrl: './categories-menu.component.html',
|
templateUrl: './categories-menu.component.html',
|
||||||
styleUrl: './categories-menu.component.scss'
|
styleUrl: './categories-menu.component.scss'
|
||||||
})
|
})
|
||||||
export class CategoriesMenuComponent implements OnInit {
|
export class CategoriesMenuComponent implements OnInit, OnDestroy {
|
||||||
private sideMenuService = inject(SideMenuService);
|
|
||||||
@Output()
|
@Output()
|
||||||
categoryClicked = new EventEmitter<void>();
|
categoryClicked = new EventEmitter<void>();
|
||||||
|
private sideMenuService = inject(SideMenuService);
|
||||||
|
private categoriesSubscription?: Subscription;
|
||||||
|
categories = signal<DisplayableCategory[]>([]);
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.categoriesSubscription = this.sideMenuService.categories$
|
||||||
|
.subscribe(categories => this.categories.set(categories));
|
||||||
this.sideMenuService.loadCategories();
|
this.sideMenuService.loadCategories();
|
||||||
}
|
}
|
||||||
|
|
||||||
get categories$(): Observable<DisplayableCategory[]> {
|
ngOnDestroy(): void {
|
||||||
return this.sideMenuService.categories$;
|
this.categoriesSubscription?.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
setOpenned(category: DisplayableCategory): void {
|
setOpenned(category: DisplayableCategory): void {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="menu {{ isOpenned ? 'displayed' : '' }}">
|
<div class="menu {{ isOpenned() ? 'displayed' : '' }}">
|
||||||
<h1>
|
<h1>
|
||||||
<a [routerLink]="['/home']">
|
<a [routerLink]="['/home']">
|
||||||
<img src="assets/images/codiki.png" alt="logo"/>
|
<img src="assets/images/codiki.png" alt="logo"/>
|
||||||
@@ -11,4 +11,4 @@
|
|||||||
<h2 i18n>Categories</h2>
|
<h2 i18n>Categories</h2>
|
||||||
<app-categories-menu (categoryClicked)="close()"></app-categories-menu>
|
<app-categories-menu (categoryClicked)="close()"></app-categories-menu>
|
||||||
</div>
|
</div>
|
||||||
<div class="overlay {{ isOpenned ? 'displayed' : ''}}" (click)="close()"></div>
|
<div class="overlay {{ isOpenned() ? 'displayed' : ''}}" (click)="close()"></div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, signal } from '@angular/core';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
@@ -12,13 +12,13 @@ import { CategoriesMenuComponent } from './categories-menu/categories-menu.compo
|
|||||||
styleUrl: './side-menu.component.scss'
|
styleUrl: './side-menu.component.scss'
|
||||||
})
|
})
|
||||||
export class SideMenuComponent {
|
export class SideMenuComponent {
|
||||||
isOpenned: boolean = false;
|
isOpenned = signal(false);
|
||||||
|
|
||||||
open(): void {
|
open(): void {
|
||||||
this.isOpenned = true;
|
this.isOpenned.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
close(): void {
|
close(): void {
|
||||||
this.isOpenned = false;
|
this.isOpenned.set(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import { inject } from "@angular/core";
|
|
||||||
import { CanActivateFn, Router } from "@angular/router";
|
|
||||||
import { AuthenticationService } from "../service/authentication.service";
|
|
||||||
import { MatSnackBar } from "@angular/material/snack-bar";
|
|
||||||
|
|
||||||
export const alreadyAuthenticatedGuard: CanActivateFn = () => {
|
|
||||||
const authenticationService = inject(AuthenticationService);
|
|
||||||
const router = inject(Router);
|
|
||||||
const snackBar = inject(MatSnackBar);
|
|
||||||
|
|
||||||
if (authenticationService.isAuthenticated()) {
|
|
||||||
router.navigate(['/home']);
|
|
||||||
snackBar.open($localize`You can't access to this page because you are already authenticated.`, $localize`Close`, { duration: 5000 });
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,13 +3,11 @@ import { CanActivateFn, Router } from "@angular/router";
|
|||||||
import { AuthenticationService } from "../service/authentication.service";
|
import { AuthenticationService } from "../service/authentication.service";
|
||||||
import { MatSnackBar } from "@angular/material/snack-bar";
|
import { MatSnackBar } from "@angular/material/snack-bar";
|
||||||
|
|
||||||
export const authenticationGuard: CanActivateFn = async () => {
|
export const authenticationGuard: CanActivateFn = () => {
|
||||||
const authenticationService = inject(AuthenticationService);
|
const authenticationService = inject(AuthenticationService);
|
||||||
const router = inject(Router);
|
const router = inject(Router);
|
||||||
const snackBar = inject(MatSnackBar);
|
const snackBar = inject(MatSnackBar);
|
||||||
|
|
||||||
await authenticationService.checkIsAuthenticated();
|
|
||||||
|
|
||||||
if (authenticationService.isAuthenticated()) {
|
if (authenticationService.isAuthenticated()) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -41,12 +41,23 @@ export class JwtInterceptor implements HttpInterceptor {
|
|||||||
this.isRefreshingToken = true;
|
this.isRefreshingToken = true;
|
||||||
this.refreshTokenSubject.next(undefined);
|
this.refreshTokenSubject.next(undefined);
|
||||||
|
|
||||||
this.authenticationService.refreshToken()
|
const refreshToken = this.authenticationService.getRefreshToken();
|
||||||
|
if (refreshToken) {
|
||||||
|
const refreshTokenRequest: RefreshTokenRequest = {
|
||||||
|
refreshTokenValue: refreshToken
|
||||||
|
};
|
||||||
|
this.userRestService.refreshToken(refreshTokenRequest)
|
||||||
.then(refreshTokenResponse => {
|
.then(refreshTokenResponse => {
|
||||||
|
this.authenticationService.authenticate(refreshTokenResponse.accessToken, refreshTokenResponse.refreshToken);
|
||||||
this.refreshTokenSubject.next(refreshTokenResponse.accessToken);
|
this.refreshTokenSubject.next(refreshTokenResponse.accessToken);
|
||||||
})
|
})
|
||||||
.catch(() => this.handleNoRefreshToken(initialError))
|
.catch(() => {
|
||||||
|
return this.handleNoRefreshToken(initialError);
|
||||||
|
})
|
||||||
.finally(() => this.isRefreshingToken = false);
|
.finally(() => this.isRefreshingToken = false);
|
||||||
|
} else {
|
||||||
|
return this.handleNoRefreshToken(initialError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.refreshTokenSubject.pipe(
|
return this.refreshTokenSubject.pipe(
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { inject, Injectable } from "@angular/core";
|
import { inject, Injectable } from "@angular/core";
|
||||||
import { BehaviorSubject, interval } from "rxjs";
|
|
||||||
import { User } from "../model/User";
|
import { User } from "../model/User";
|
||||||
import { UserRestService } from "../rest-services/user/user.rest-service";
|
import { UserRestService } from "../rest-services/user/user.rest-service";
|
||||||
import { RefreshTokenRequest } from "../rest-services/user/model/refresh-token.model";
|
import { RefreshTokenRequest } from "../rest-services/user/model/refresh-token.model";
|
||||||
import { LoginResponse } from "../rest-services/user/model/login.model";
|
|
||||||
|
|
||||||
const JWT_PARAM = 'jwt';
|
const JWT_PARAM = 'jwt';
|
||||||
const REFRESH_TOKEN_PARAM = 'refresh-token';
|
const REFRESH_TOKEN_PARAM = 'refresh-token';
|
||||||
@@ -20,30 +18,18 @@ interface UserDetails {
|
|||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class AuthenticationService {
|
export class AuthenticationService {
|
||||||
private readonly AUTHENTICATION_CHECKING_PERIOD = 5 * 60 * 1000;
|
|
||||||
private isAuthenticatedSubject = new BehaviorSubject<boolean>(false);
|
|
||||||
private readonly userRestService = inject(UserRestService);
|
|
||||||
|
|
||||||
startAuthenticationCheckingProcess(): void {
|
|
||||||
this.checkIsAuthenticated();
|
|
||||||
interval(this.AUTHENTICATION_CHECKING_PERIOD)
|
|
||||||
.subscribe(() => this.checkIsAuthenticated());
|
|
||||||
}
|
|
||||||
|
|
||||||
authenticate(token: string, refreshToken: string): void {
|
authenticate(token: string, refreshToken: string): void {
|
||||||
localStorage.setItem(JWT_PARAM, token);
|
localStorage.setItem(JWT_PARAM, token);
|
||||||
localStorage.setItem(REFRESH_TOKEN_PARAM, refreshToken);
|
localStorage.setItem(REFRESH_TOKEN_PARAM, refreshToken);
|
||||||
this.isAuthenticatedSubject.next(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unauthenticate(): void {
|
unauthenticate(): void {
|
||||||
localStorage.removeItem(JWT_PARAM);
|
localStorage.removeItem(JWT_PARAM);
|
||||||
localStorage.removeItem(REFRESH_TOKEN_PARAM);
|
|
||||||
this.isAuthenticatedSubject.next(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isAuthenticated(): boolean {
|
isAuthenticated(): boolean {
|
||||||
return this.isAuthenticatedSubject.value;
|
return !!localStorage.getItem(JWT_PARAM);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthenticatedUser(): User | undefined {
|
getAuthenticatedUser(): User | undefined {
|
||||||
@@ -59,7 +45,7 @@ export class AuthenticationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isTokenExpired(): boolean {
|
isTokenExpired(): boolean {
|
||||||
let result = true;
|
let result = false;
|
||||||
|
|
||||||
const userDetails = this.extractUserDetails();
|
const userDetails = this.extractUserDetails();
|
||||||
|
|
||||||
@@ -73,43 +59,8 @@ export class AuthenticationService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshToken(): Promise<LoginResponse> {
|
|
||||||
const refreshToken = this.getRefreshToken();
|
|
||||||
if (refreshToken) {
|
|
||||||
const refreshTokenRequest: RefreshTokenRequest = {
|
|
||||||
refreshTokenValue: refreshToken
|
|
||||||
};
|
|
||||||
return this.userRestService.refreshToken(refreshTokenRequest)
|
|
||||||
.then(refreshTokenResponse => {
|
|
||||||
this.authenticate(refreshTokenResponse.accessToken, refreshTokenResponse.refreshToken);
|
|
||||||
return refreshTokenResponse;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject('No any refresh token found.');
|
|
||||||
}
|
|
||||||
|
|
||||||
checkIsAuthenticated(): Promise<void> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const isTokenExpired = this.isTokenExpired();
|
|
||||||
if (isTokenExpired) {
|
|
||||||
if (this.getRefreshToken()) {
|
|
||||||
this.refreshToken()
|
|
||||||
.then(() => resolve())
|
|
||||||
.catch(() => reject());
|
|
||||||
} else {
|
|
||||||
this.isAuthenticatedSubject.next(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.isAuthenticatedSubject.next(true);
|
|
||||||
}
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private extractUserFromLocalStorage(): User | undefined {
|
private extractUserFromLocalStorage(): User | undefined {
|
||||||
let result: User | undefined;
|
let result: User | undefined = undefined;
|
||||||
|
|
||||||
const userDetails = this.extractUserDetails();
|
const userDetails = this.extractUserDetails();
|
||||||
if (userDetails) {
|
if (userDetails) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<h1 i18n>Last publications</h1>
|
<h1 i18n>Last publications</h1>
|
||||||
@if ((isLoading$ | async) === true) {
|
@if (isLoading()) {
|
||||||
<h2 i18n>Publications loading...</h2>
|
<h2 i18n>Publications loading...</h2>
|
||||||
<mat-spinner></mat-spinner>
|
<mat-spinner></mat-spinner>
|
||||||
} @else {
|
} @else {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Component, OnInit, inject } from '@angular/core';
|
import { Component, OnDestroy, OnInit, inject, signal } from '@angular/core';
|
||||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
import { PublicationListComponent } from '../../components/publication-list/publication-list.component';
|
import { PublicationListComponent } from '../../components/publication-list/publication-list.component';
|
||||||
import { Publication } from '../../core/rest-services/publications/model/publication';
|
import { Publication } from '../../core/rest-services/publications/model/publication';
|
||||||
import { HomeService } from './home.service';
|
import { HomeService } from './home.service';
|
||||||
@@ -18,18 +18,27 @@ import { CommonModule } from '@angular/common';
|
|||||||
styleUrl: './home.component.scss',
|
styleUrl: './home.component.scss',
|
||||||
providers: [HomeService]
|
providers: [HomeService]
|
||||||
})
|
})
|
||||||
export class HomeComponent implements OnInit {
|
export class HomeComponent implements OnInit, OnDestroy {
|
||||||
private homeService = inject(HomeService);
|
private homeService = inject(HomeService);
|
||||||
|
private isLoadingSubscription?: Subscription;
|
||||||
get isLoading$(): Observable<boolean> {
|
private publicationsSubscription?: Subscription;
|
||||||
return this.homeService.isLoading$;
|
isLoading = signal(false);
|
||||||
}
|
publicationsIsEmpty = signal(true);
|
||||||
|
|
||||||
get publications$(): Observable<Publication[]> {
|
get publications$(): Observable<Publication[]> {
|
||||||
return this.homeService.publications$;
|
return this.homeService.publications$;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.isLoadingSubscription = this.homeService.isLoading$
|
||||||
|
.subscribe(isLoading => this.isLoading.set(isLoading));
|
||||||
|
this.publicationsSubscription = this.homeService.publications$
|
||||||
|
.subscribe(publications => this.publicationsIsEmpty.set(publications.length > 0));
|
||||||
this.homeService.startLatestPublicationsRetrieving();
|
this.homeService.startLatestPublicationsRetrieving();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.isLoadingSubscription?.unsubscribe();
|
||||||
|
this.publicationsSubscription?.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
+
|
+
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@if ((isLoading$ | async) === true) {
|
@if (isLoading()) {
|
||||||
<h2 i18n>Publication loading...</h2>
|
<h2 i18n>Publication loading...</h2>
|
||||||
<mat-spinner></mat-spinner>
|
<mat-spinner></mat-spinner>
|
||||||
} @else {
|
} @else {
|
||||||
@if ((isLoaded$ | async) === true) {
|
@if (isLoaded()) {
|
||||||
<app-publication-list [publications$]="publications$"></app-publication-list>
|
<app-publication-list [publications$]="publications$"></app-publication-list>
|
||||||
} @else {
|
} @else {
|
||||||
<h2 i18n>There is no any publication...</h2>
|
<h2 i18n>There is no any publication...</h2>
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { Component, inject, OnInit } from "@angular/core";
|
import { Component, inject, OnDestroy, OnInit, signal } from "@angular/core";
|
||||||
import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
|
import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
|
||||||
import { MyPublicationsService } from "./my-publications.service";
|
import { MyPublicationsService } from "./my-publications.service";
|
||||||
import { Observable } from "rxjs";
|
import { Observable, Subscription } from "rxjs";
|
||||||
import { PublicationListComponent } from "../../components/publication-list/publication-list.component";
|
import { PublicationListComponent } from "../../components/publication-list/publication-list.component";
|
||||||
import { Publication } from "../../core/rest-services/publications/model/publication";
|
import { Publication } from "../../core/rest-services/publications/model/publication";
|
||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { RouterModule } from "@angular/router";
|
import { RouterModule } from "@angular/router";
|
||||||
import { MatTooltipModule } from "@angular/material/tooltip";
|
import { MatTooltipModule } from "@angular/material/tooltip";
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-my-component',
|
selector: 'app-my-component',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@@ -23,22 +22,28 @@ import { MatTooltipModule } from "@angular/material/tooltip";
|
|||||||
],
|
],
|
||||||
providers: [MyPublicationsService]
|
providers: [MyPublicationsService]
|
||||||
})
|
})
|
||||||
export class MyPublicationsComponent implements OnInit {
|
export class MyPublicationsComponent implements OnInit, OnDestroy {
|
||||||
private readonly myPublicationsService = inject(MyPublicationsService);
|
private readonly myPublicationsService = inject(MyPublicationsService);
|
||||||
|
private isLoadingSubscription?: Subscription;
|
||||||
|
private isLoadedSubscription?: Subscription;
|
||||||
|
|
||||||
|
isLoading = signal(false);
|
||||||
|
isLoaded = signal(false);
|
||||||
|
|
||||||
get publications$(): Observable<Publication[]> {
|
get publications$(): Observable<Publication[]> {
|
||||||
return this.myPublicationsService.publications$;
|
return this.myPublicationsService.publications$;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isLoading$(): Observable<boolean> {
|
|
||||||
return this.myPublicationsService.isLoading$;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isLoaded$(): Observable<boolean> {
|
|
||||||
return this.myPublicationsService.isLoaded$;
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.isLoadingSubscription = this.myPublicationsService.isLoading$
|
||||||
|
.subscribe(isLoading => this.isLoading.set(isLoading));
|
||||||
|
this.isLoadedSubscription = this.myPublicationsService.isLoaded$
|
||||||
|
.subscribe(isLoaded => this.isLoaded.set(isLoaded));
|
||||||
this.myPublicationsService.loadAuthenticatedUserPublications();
|
this.myPublicationsService.loadAuthenticatedUserPublications();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.isLoadingSubscription?.unsubscribe();
|
||||||
|
this.isLoadedSubscription?.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
<app-publication-edition
|
@if (publicationIsNotNull()) {
|
||||||
|
<app-publication-edition
|
||||||
title="Creation of a new publication"
|
title="Creation of a new publication"
|
||||||
[publication]="publication"
|
[publication]="publication()!!"
|
||||||
[isSaving$]="isSaving$"
|
[isSaving$]="isSaving$"
|
||||||
(publicationSave)="onPublicationSave($event)"
|
(publicationSave)="onPublicationSave($event)"
|
||||||
i18n-title>
|
i18n-title>
|
||||||
</app-publication-edition>
|
</app-publication-edition>
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, inject, OnInit } from "@angular/core";
|
import { Component, computed, inject, OnInit, signal } from "@angular/core";
|
||||||
import { PublicationEditionComponent } from "../../components/publication-edition/publication-edition.component";
|
import { PublicationEditionComponent } from "../../components/publication-edition/publication-edition.component";
|
||||||
import { PublicationRestService } from "../../core/rest-services/publications/publication.rest-service";
|
import { PublicationRestService } from "../../core/rest-services/publications/publication.rest-service";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
@@ -26,7 +26,8 @@ export class PublicationCreationComponent implements OnInit {
|
|||||||
private readonly snackBar = inject(MatSnackBar);
|
private readonly snackBar = inject(MatSnackBar);
|
||||||
private isSavingSubject = new BehaviorSubject<boolean>(false);
|
private isSavingSubject = new BehaviorSubject<boolean>(false);
|
||||||
private subscriptions: Subscription[] = [];
|
private subscriptions: Subscription[] = [];
|
||||||
publication!: Publication;
|
publication = signal<Publication | undefined>(undefined);
|
||||||
|
publicationIsNotNull = computed(() => !!this.publication());
|
||||||
|
|
||||||
get isSaving$(): Observable<boolean> {
|
get isSaving$(): Observable<boolean> {
|
||||||
return this.isSavingSubject.asObservable();
|
return this.isSavingSubject.asObservable();
|
||||||
@@ -40,7 +41,7 @@ export class PublicationCreationComponent implements OnInit {
|
|||||||
name: authenticatedUser.pseudo,
|
name: authenticatedUser.pseudo,
|
||||||
image: authenticatedUser.photoId ?? ''
|
image: authenticatedUser.photoId ?? ''
|
||||||
};
|
};
|
||||||
this.publication = {
|
const newPublication: Publication = {
|
||||||
id: '',
|
id: '',
|
||||||
key: '',
|
key: '',
|
||||||
title: '',
|
title: '',
|
||||||
@@ -52,6 +53,7 @@ export class PublicationCreationComponent implements OnInit {
|
|||||||
categoryId: '',
|
categoryId: '',
|
||||||
author
|
author
|
||||||
};
|
};
|
||||||
|
this.publication.set(newPublication);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@if ((isLoading$ | async) == true) {
|
@if (isLoading()) {
|
||||||
<h2 i18n>Loading publication to edit...</h2>
|
<h2 i18n>Loading publication to edit...</h2>
|
||||||
<mat-spinner></mat-spinner>
|
<mat-spinner></mat-spinner>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { CommonModule, Location } from '@angular/common';
|
import { CommonModule, Location } from '@angular/common';
|
||||||
import { Component, inject, OnDestroy, OnInit } from '@angular/core';
|
import { Component, inject, OnDestroy, OnInit, signal } from '@angular/core';
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
@@ -41,21 +41,17 @@ export class PublicationUpdateComponent implements OnInit, OnDestroy {
|
|||||||
private readonly location = inject(Location);
|
private readonly location = inject(Location);
|
||||||
private readonly router = inject(Router);
|
private readonly router = inject(Router);
|
||||||
private readonly snackBar = inject(MatSnackBar);
|
private readonly snackBar = inject(MatSnackBar);
|
||||||
private isLoadingSubject = new BehaviorSubject<boolean>(false);
|
|
||||||
private isSavingSubject = new BehaviorSubject<boolean>(false);
|
private isSavingSubject = new BehaviorSubject<boolean>(false);
|
||||||
private subscriptions: Subscription[] = [];
|
private subscriptions: Subscription[] = [];
|
||||||
|
isLoading = signal(false);
|
||||||
publication: Publication | undefined;
|
publication: Publication | undefined;
|
||||||
|
|
||||||
get isLoading$(): Observable<boolean> {
|
|
||||||
return this.isLoadingSubject.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
get isSaving$(): Observable<boolean> {
|
get isSaving$(): Observable<boolean> {
|
||||||
return this.isSavingSubject.asObservable();
|
return this.isSavingSubject.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.isLoadingSubject.next(true);
|
this.isLoading.set(true);
|
||||||
this.activatedRoute.paramMap.subscribe(params => {
|
this.activatedRoute.paramMap.subscribe(params => {
|
||||||
const publicationId = params.get('publicationId');
|
const publicationId = params.get('publicationId');
|
||||||
if (publicationId == undefined) {
|
if (publicationId == undefined) {
|
||||||
@@ -71,7 +67,7 @@ export class PublicationUpdateComponent implements OnInit, OnDestroy {
|
|||||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||||
console.error(errorMessage, error)
|
console.error(errorMessage, error)
|
||||||
})
|
})
|
||||||
.finally(() => this.isLoadingSubject.next(false));
|
.finally(() => this.isLoading.set(false));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
@if (isLoading) {
|
@if (isLoading()) {
|
||||||
<h2 i18n>Publication content loading...</h2>
|
<h2 i18n>Publication content loading...</h2>
|
||||||
<mat-spinner></mat-spinner>
|
<mat-spinner></mat-spinner>
|
||||||
} @else {
|
} @else {
|
||||||
@if (publication) {
|
@if (isPublicationUndefined()) {
|
||||||
|
<div class="loading-failed">
|
||||||
|
<h1 i18n>Publication failed to load...</h1>
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<img src="/api/pictures/{{ publication.illustrationId }}" />
|
<img src="/api/pictures/{{ publication()?.illustrationId }}" />
|
||||||
<header>
|
<header>
|
||||||
<h1>{{ publication.title }}</h1>
|
<h1>{{ publication()?.title }}</h1>
|
||||||
<h2>{{ publication.description }}</h2>
|
<h2>{{ publication()?.description }}</h2>
|
||||||
@if (isAuthorAndUserEquals) {
|
@if (isAuthorAndUserEquals()) {
|
||||||
<a [routerLink]="['edit']"
|
<a [routerLink]="['edit']"
|
||||||
class="button action"
|
class="button action"
|
||||||
matTooltip="Click to edit the publication"
|
matTooltip="Click to edit the publication"
|
||||||
@@ -17,18 +21,18 @@
|
|||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
</header>
|
</header>
|
||||||
<main [innerHTML]="publication.parsedText"></main>
|
<main [innerHTML]="publication()?.parsedText"></main>
|
||||||
<footer>
|
<footer>
|
||||||
<div class="metadata">
|
<div class="metadata">
|
||||||
<img src="/api/pictures/{{ publication.author.image }}" [matTooltip]="publication.author.name" />
|
<img src="/api/pictures/{{ publication()?.author?.image }}" [matTooltip]="publication()?.author?.name" />
|
||||||
<div class="posting-data">
|
<div class="posting-data">
|
||||||
<span i18n>Publication posted by {{ publication.author.name }}</span>
|
<span i18n>Publication posted by {{ publication()?.author?.name }}</span>
|
||||||
<span class="publication-date">
|
<span class="publication-date">
|
||||||
({{ publication.creationDate | date: 'short' }})
|
({{ publication()?.creationDate | date: 'short' }})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (isAuthorAndUserEquals) {
|
@if (isAuthorAndUserEquals()) {
|
||||||
<button type="button"
|
<button type="button"
|
||||||
(click)="deletePublication()"
|
(click)="deletePublication()"
|
||||||
matTooltip="Click to delete the publication"
|
matTooltip="Click to delete the publication"
|
||||||
@@ -40,9 +44,5 @@
|
|||||||
}
|
}
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
|
||||||
<div class="loading-failed">
|
|
||||||
<h1 i18n>Publication failed to load...</h1>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,8 @@ $cardBorderRadius: .5em;
|
|||||||
|
|
||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnDestroy, OnInit, inject } from '@angular/core';
|
import { Component, OnDestroy, OnInit, computed, inject, signal } from '@angular/core';
|
||||||
import { PublicationRestService } from '../../core/rest-services/publications/publication.rest-service';
|
import { PublicationRestService } from '../../core/rest-services/publications/publication.rest-service';
|
||||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
@@ -30,9 +30,10 @@ export class PublicationComponent implements OnInit, OnDestroy {
|
|||||||
private readonly snackBar = inject(MatSnackBar);
|
private readonly snackBar = inject(MatSnackBar);
|
||||||
private paramMapSubscription?: Subscription;
|
private paramMapSubscription?: Subscription;
|
||||||
private afterDialogCloseSubscription?: Subscription;
|
private afterDialogCloseSubscription?: Subscription;
|
||||||
isLoading: boolean = false;
|
isLoading = signal(false);
|
||||||
isAuthorAndUserEquals: boolean = false;
|
isAuthorAndUserEquals = computed(() => this.authenticationService.getAuthenticatedUser()?.id === this.publication()?.author.id);
|
||||||
publication?: Publication;
|
publication = signal<Publication | undefined>(undefined);
|
||||||
|
isPublicationUndefined = computed(() => !this.publication())
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.paramMapSubscription = this.activatedRoute
|
this.paramMapSubscription = this.activatedRoute
|
||||||
@@ -41,12 +42,11 @@ export class PublicationComponent implements OnInit, OnDestroy {
|
|||||||
const publicationId = params.get('publicationId');
|
const publicationId = params.get('publicationId');
|
||||||
|
|
||||||
if (publicationId) {
|
if (publicationId) {
|
||||||
this.isLoading = true;
|
this.isLoading.set(true);
|
||||||
|
|
||||||
this.publicationRestService.getById(publicationId)
|
this.publicationRestService.getById(publicationId)
|
||||||
.then(publication => {
|
.then(publication => {
|
||||||
this.publication = publication;
|
this.publication.set(publication);
|
||||||
this.isAuthorAndUserEquals = this.authenticationService.getAuthenticatedUser()?.id === this.publication.author.id;
|
|
||||||
setTimeout(() => Prism.highlightAll(), 100);
|
setTimeout(() => Prism.highlightAll(), 100);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@@ -55,7 +55,7 @@ export class PublicationComponent implements OnInit, OnDestroy {
|
|||||||
console.error(errorMessage, error);
|
console.error(errorMessage, error);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.isLoading = false;
|
this.isLoading.set(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -79,8 +79,8 @@ export class PublicationComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.afterDialogCloseSubscription = dialogRef.afterClosed()
|
this.afterDialogCloseSubscription = dialogRef.afterClosed()
|
||||||
.subscribe(response => {
|
.subscribe(response => {
|
||||||
if (response && this.publication?.id) {
|
if (response && !this.isPublicationUndefined()) {
|
||||||
this.publicationRestService.delete(this.publication.id);
|
this.publicationRestService.delete(this.publication()?.id!!);
|
||||||
this.snackBar.open($localize`Publication deleted`, $localize`Close`, { duration: 5000 });
|
this.snackBar.open($localize`Publication deleted`, $localize`Close`, { duration: 5000 });
|
||||||
this.location.back();
|
this.location.back();
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
@@ -46,7 +46,6 @@
|
|||||||
"9122763438636464100": "Fermer le menu",
|
"9122763438636464100": "Fermer le menu",
|
||||||
"1902100407096396858": "Catégories",
|
"1902100407096396858": "Catégories",
|
||||||
"6940115735259407353": "Une erreur est survenue lors du chargement des catégories.",
|
"6940115735259407353": "Une erreur est survenue lors du chargement des catégories.",
|
||||||
"3516191632292372377": "Vous ne pouvez pas accéder à cette page car vous êtes déjà connecté.",
|
|
||||||
"3450287383703155559": "Vous n'êtes pas connecté. Veuillez vous connecter avant de réessayer.",
|
"3450287383703155559": "Vous n'êtes pas connecté. Veuillez vous connecter avant de réessayer.",
|
||||||
"5455465794443528807": "You n'êtes pas connecté. Veuillez vous connecter avant de réessayer votre opération.",
|
"5455465794443528807": "You n'êtes pas connecté. Veuillez vous connecter avant de réessayer votre opération.",
|
||||||
"4011987306265136481": "Déconnexion...",
|
"4011987306265136481": "Déconnexion...",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"locale": "en",
|
"locale": "en-UK",
|
||||||
"translations": {
|
"translations": {
|
||||||
"3603720768157919481": " No ",
|
"3603720768157919481": " No ",
|
||||||
"4861926948802653243": " Yes ",
|
"4861926948802653243": " Yes ",
|
||||||
@@ -46,7 +46,6 @@
|
|||||||
"9122763438636464100": "Close the menu",
|
"9122763438636464100": "Close the menu",
|
||||||
"1902100407096396858": "Categories",
|
"1902100407096396858": "Categories",
|
||||||
"6940115735259407353": "An error occured while loading categories.",
|
"6940115735259407353": "An error occured while loading categories.",
|
||||||
"3516191632292372377": "You can't access to this page because you are already authenticated.",
|
|
||||||
"3450287383703155559": "You are unauthenticated. Please, log-in first.",
|
"3450287383703155559": "You are unauthenticated. Please, log-in first.",
|
||||||
"5455465794443528807": "You are unauthenticated. Please, re-authenticate before retrying your action.",
|
"5455465794443528807": "You are unauthenticated. Please, re-authenticate before retrying your action.",
|
||||||
"4011987306265136481": "Disconnection...",
|
"4011987306265136481": "Disconnection...",
|
||||||
|
|||||||
Reference in New Issue
Block a user