Re-design of login page.
This commit is contained in:
@@ -9,6 +9,10 @@ export const routes: Routes = [
|
||||
path: 'disconnect',
|
||||
loadComponent: () => import('./pages/disconnection/disconnection.component').then(module => module.DisconnectionComponent)
|
||||
},
|
||||
{
|
||||
path: 'publications/:publicationId',
|
||||
loadComponent: () => import('./pages/publication/publication.component').then(module => module.PublicationComponent)
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
loadComponent: () => import('./pages/home/home.component').then(module => module.HomeComponent)
|
||||
|
||||
@@ -12,4 +12,8 @@ export class PublicationRestService {
|
||||
getLatest(): Promise<Publication[]> {
|
||||
return lastValueFrom(this.httpClient.get<Publication[]>('/api/publications/latest'));
|
||||
}
|
||||
|
||||
getById(publicationId: string): Promise<Publication> {
|
||||
return lastValueFrom(this.httpClient.get<Publication>(`/api/publications/${publicationId}`));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
<form [formGroup]="loginForm" (submit)="performLogin()" ngNativeValidate>
|
||||
<h1>Login</h1>
|
||||
<div>
|
||||
<mat-icon>person</mat-icon>
|
||||
<label for="email">
|
||||
Email address
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
<input type="email" formControlName="email" autocomplete="email" required />
|
||||
<input type="email" id="email" formControlName="email" autocomplete="email" required />
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon>lock</mat-icon>
|
||||
<label for="password">
|
||||
Password
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
<input type="password" formControlName="password" required />
|
||||
<input type="password" id="password" formControlName="password" required />
|
||||
</div>
|
||||
<div class="actions">
|
||||
<a [routerLink]="['/home']">Create an account</a>
|
||||
<button type="submit">Send</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -3,34 +3,78 @@
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1em;
|
||||
|
||||
form {
|
||||
min-width: 40em;
|
||||
max-width: 90%;
|
||||
width: 80%;
|
||||
max-width: 20em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
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 {
|
||||
display: flex;
|
||||
gap: .5em;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
gap: .1em;
|
||||
|
||||
mat-icon {
|
||||
position: absolute;
|
||||
top: 1.2em;
|
||||
left: .5em;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
&.actions {
|
||||
justify-content: end;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
a {
|
||||
color: #3f51b5;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 10em;
|
||||
height: 2em;
|
||||
padding: .8em 1.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 {
|
||||
flex: 1 30%;
|
||||
flex: 1;
|
||||
font-style: italic;
|
||||
padding-left: 1em;
|
||||
color: #777;
|
||||
|
||||
.required {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1 70%;
|
||||
flex: 1;
|
||||
background-color: #eeeeee;
|
||||
border: none;
|
||||
border-radius: 10em;
|
||||
padding: 1em 1em 1em 3em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,15 @@ import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators }
|
||||
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';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
standalone: true,
|
||||
templateUrl: './login.component.html',
|
||||
styleUrl: './login.component.scss',
|
||||
imports: [ ReactiveFormsModule ],
|
||||
imports: [ReactiveFormsModule, MatIconModule, RouterModule],
|
||||
providers: [LoginService, MatSnackBarModule]
|
||||
})
|
||||
export class LoginComponent implements OnInit, OnDestroy {
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
frontend/src/app/pages/publication/publication.component.ts
Normal file
55
frontend/src/app/pages/publication/publication.component.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user