Re-design of login page.

This commit is contained in:
Florian THIERRY
2024-06-06 11:05:02 +02:00
parent e5076f0c64
commit 95d5308934
8 changed files with 221 additions and 12 deletions

View File

@@ -9,6 +9,10 @@ export const routes: Routes = [
path: 'disconnect', path: 'disconnect',
loadComponent: () => import('./pages/disconnection/disconnection.component').then(module => module.DisconnectionComponent) loadComponent: () => import('./pages/disconnection/disconnection.component').then(module => module.DisconnectionComponent)
}, },
{
path: 'publications/:publicationId',
loadComponent: () => import('./pages/publication/publication.component').then(module => module.PublicationComponent)
},
{ {
path: '**', path: '**',
loadComponent: () => import('./pages/home/home.component').then(module => module.HomeComponent) loadComponent: () => import('./pages/home/home.component').then(module => module.HomeComponent)

View File

@@ -12,4 +12,8 @@ export class PublicationRestService {
getLatest(): Promise<Publication[]> { getLatest(): Promise<Publication[]> {
return lastValueFrom(this.httpClient.get<Publication[]>('/api/publications/latest')); return lastValueFrom(this.httpClient.get<Publication[]>('/api/publications/latest'));
} }
getById(publicationId: string): Promise<Publication> {
return lastValueFrom(this.httpClient.get<Publication>(`/api/publications/${publicationId}`));
}
} }

View File

@@ -1,18 +1,23 @@
<form [formGroup]="loginForm" (submit)="performLogin()" ngNativeValidate> <form [formGroup]="loginForm" (submit)="performLogin()" ngNativeValidate>
<h1>Login</h1> <h1>Login</h1>
<div> <div>
<mat-icon>person</mat-icon>
<label for="email"> <label for="email">
Email address Email address
<span class="required">*</span>
</label> </label>
<input type="email" formControlName="email" autocomplete="email" required /> <input type="email" id="email" formControlName="email" autocomplete="email" required />
</div> </div>
<div> <div>
<mat-icon>lock</mat-icon>
<label for="password"> <label for="password">
Password Password
<span class="required">*</span>
</label> </label>
<input type="password" formControlName="password" required /> <input type="password" id="password" formControlName="password" required />
</div> </div>
<div class="actions"> <div class="actions">
<a [routerLink]="['/home']">Create an account</a>
<button type="submit">Send</button> <button type="submit">Send</button>
</div> </div>
</form> </form>

View File

@@ -3,34 +3,78 @@
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding: 1em;
form { form {
min-width: 40em; width: 80%;
max-width: 90%; max-width: 20em;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
gap: .5em; gap: 1em;
box-shadow: 0 2px 5px 0 rgba(0,0,0,.16),0 2px 10px 0 rgba(0,0,0,.12);
border-radius: .5em;
padding: 1em 1.5em;
h1 {
margin: 0;
}
div { div {
display: flex; display: flex;
gap: .5em; flex-direction: column;
position: relative;
gap: .1em;
mat-icon {
position: absolute;
top: 1.2em;
left: .5em;
color: #777;
}
&.actions { &.actions {
justify-content: end; flex-direction: row;
justify-content: space-between;
align-items: center;
a {
color: #3f51b5;
text-decoration: none;
}
button { button {
width: 10em; padding: .8em 1.2em;
height: 2em; border-radius: 10em;
border: none;
background-color: #3f51b5;
color: white;
transition: background-color .2s ease-in-out;
&:hover {
background-color: #5b6ed8;
cursor: pointer;
}
} }
} }
label { label {
flex: 1 30%; flex: 1;
font-style: italic;
padding-left: 1em;
color: #777;
.required {
color: red;
}
} }
input { input {
flex: 1 70%; flex: 1;
background-color: #eeeeee;
border: none;
border-radius: 10em;
padding: 1em 1em 1em 3em;
} }
} }
} }

View File

@@ -3,13 +3,15 @@ import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators }
import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatSnackBarModule } from '@angular/material/snack-bar';
import { Subscription, debounceTime, map } from 'rxjs'; import { Subscription, debounceTime, map } from 'rxjs';
import { LoginService } from './login.service'; import { LoginService } from './login.service';
import { MatIconModule } from '@angular/material/icon';
import { RouterModule } from '@angular/router';
@Component({ @Component({
selector: 'app-login', selector: 'app-login',
standalone: true, standalone: true,
templateUrl: './login.component.html', templateUrl: './login.component.html',
styleUrl: './login.component.scss', styleUrl: './login.component.scss',
imports: [ ReactiveFormsModule ], imports: [ReactiveFormsModule, MatIconModule, RouterModule],
providers: [LoginService, MatSnackBarModule] providers: [LoginService, MatSnackBarModule]
}) })
export class LoginComponent implements OnInit, OnDestroy { export class LoginComponent implements OnInit, OnDestroy {

View File

@@ -0,0 +1,29 @@
<ng-container *ngIf="isLoading; else afterLoadingPart">
<mat-spinner></mat-spinner>
</ng-container>
<ng-template #afterLoadingPart>
<ng-container *ngIf="publication; else loadingFailedMessage">
<div class="card">
<img src="/pictures/{{ publication.illustrationId }}" />
<header>
<h1>{{ publication.title }}</h1>
<h2>{{ publication.description }}</h2>
</header>
<main [innerHTML]="publication.parsedText"></main>
<footer>
<img src="/pictures/{{ publication.author.image }}" [matTooltip]="publication.author.name" />
Publication posted by {{ publication.author.name }}
<span class="publication-date">
({{ publication.creationDate | date: 'short' : 'fr-FR' }})
</span>
</footer>
</div>
</ng-container>
<ng-template #loadingFailedMessage>
<div class="loading-failed">
<h1>Publication failed to load...</h1>
</div>
</ng-template>
</ng-template>

View File

@@ -0,0 +1,66 @@
$cardBorderRadius: .5em;
:host {
display: flex;
justify-content: center;
.card {
display: flex;
flex-direction: column;
margin: 1em;
max-width: 80em;
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: 25em;
object-fit: cover;
border-radius: $cardBorderRadius $cardBorderRadius 0 0;
}
header {
padding: 2em;
h1 {
font-size: 2em;
margin-bottom: .5em;
}
h2 {
font-size: 1.2em;
line-height: 1.6em;
margin: 0;
font-weight: 400;
}
}
main {
border-top: 1px solid #dddddd;
margin: 0 2em 2em 2em;
padding-top: 2em;
}
footer {
display: flex;
flex-direction: row;
align-items: center;
background-color: #f0f0f0;
border-radius: 0 0 $cardBorderRadius $cardBorderRadius;
padding: 1em 2em;
gap: 1em;
img {
$imageSize: 4em;
border-radius: 10em;
width: $imageSize;
height: $imageSize;
object-fit: cover;
}
.publication-date {
font-style: italic;
color: #bdbdbd;
}
}
}
}

View File

@@ -0,0 +1,55 @@
import { Component, OnDestroy, OnInit, inject } from '@angular/core';
import { PublicationRestService } from '../../core/rest-services/publications/publication.rest-service';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { Publication } from '../../core/rest-services/publications/model/publication';
import { MatSnackBar } from '@angular/material/snack-bar';
import { CommonModule } from '@angular/common';
import { MatProgressSpinner } from '@angular/material/progress-spinner';
import { MatTooltip } from '@angular/material/tooltip';
@Component({
selector: 'app-publication',
standalone: true,
imports: [CommonModule, MatProgressSpinner, MatTooltip],
templateUrl: './publication.component.html',
styleUrl: './publication.component.scss'
})
export class PublicationComponent implements OnInit, OnDestroy {
private activatedRoute = inject(ActivatedRoute);
private publicationRestService = inject(PublicationRestService);
private paramMapSubscription?: Subscription;
private snackBar = inject(MatSnackBar);
isLoading: boolean = false;
publication?: Publication;
ngOnInit(): void {
this.paramMapSubscription = this.activatedRoute
.paramMap
.subscribe(params => {
const publicationId = params.get('publicationId');
if (publicationId) {
this.isLoading = true;
this.publicationRestService.getById(publicationId)
.then(publication => {
this.publication = publication;
})
.catch(error => {
this.snackBar.open('An error occurred while loading publication...', 'Close', { duration: 5000 });
console.error('An error occurred while loading publication...', error);
})
.finally(() => {
this.isLoading = false;
});
}
});
// this.publicationRestService.getById()
}
ngOnDestroy(): void {
this.paramMapSubscription?.unsubscribe();
}
}