frontend: redesign error page with enhanced layout, responsive design, and improved error handling

This commit is contained in:
Carlos Santos 2025-07-15 16:58:42 +02:00
parent 3ca68dae80
commit 0e4cfa5bb5
3 changed files with 256 additions and 38 deletions

View File

@ -1,10 +1,31 @@
<div class="container"> <div class="error-page">
<mat-card class="card"> <div class="error-container fade-in">
<mat-card-header> <div class="error-content">
<mat-card-title>{{ errorName }}</mat-card-title> <div class="error-icon-section">
</mat-card-header> <mat-icon class="error-main-icon">error_outline</mat-icon>
<mat-card-content> </div>
<p>{{ message }}</p>
</mat-card-content> <div class="error-details">
</mat-card> <h1 class="error-title">{{ errorName }}</h1>
<p class="error-message">{{ message }}</p>
</div>
<div class="error-actions">
<button mat-flat-button color="primary" (click)="goBack()" class="action-button">
<mat-icon>arrow_back</mat-icon>
Go Back
</button>
</div>
</div>
<div class="error-illustration">
<div class="illustration-container">
<mat-icon class="illustration-icon">cloud_off</mat-icon>
<div class="illustration-text">
<span class="illustration-title">Oops!</span>
<span class="illustration-subtitle">Something went wrong</span>
</div>
</div>
</div>
</div>
</div> </div>

View File

@ -1,33 +1,220 @@
.container { @import '../../../../../../src/assets/styles/design-tokens';
display: flex;
justify-content: center; .error-page {
align-items: center; @include ov-theme-transition;
height: 100vh; @include ov-flex-center;
background-color: var(--ov-background-color); min-height: 100vh;
background: var(--ov-meet-background-color);
padding: var(--ov-meet-spacing-lg);
.error-container {
@include ov-theme-transition;
display: flex;
flex-direction: row;
align-items: center;
gap: var(--ov-meet-spacing-xxl);
max-width: 800px;
width: 100%;
@include ov-tablet-down {
flex-direction: column;
gap: var(--ov-meet-spacing-xl);
text-align: center;
}
.error-content {
flex: 1;
display: flex;
flex-direction: column;
gap: var(--ov-meet-spacing-lg);
.error-icon-section {
@include ov-flex-center;
justify-content: flex-start;
@include ov-tablet-down {
justify-content: center;
}
.error-main-icon {
@include ov-icon(xl);
color: var(--ov-meet-color-error);
background: rgba(var(--ov-meet-color-error), 0.1);
border-radius: var(--ov-meet-radius-circle);
padding: var(--ov-meet-spacing-md);
width: 64px;
height: 64px;
font-size: 32px;
}
}
.error-details {
.error-title {
margin: 0 0 var(--ov-meet-spacing-sm) 0;
font-size: var(--ov-meet-font-size-xxl);
font-weight: var(--ov-meet-font-weight-semibold);
color: var(--ov-meet-text-primary);
line-height: var(--ov-meet-line-height-tight);
@include ov-mobile-down {
font-size: var(--ov-meet-font-size-xl);
}
}
.error-message {
margin: 0;
font-size: var(--ov-meet-font-size-md);
color: var(--ov-meet-text-secondary);
line-height: var(--ov-meet-line-height-normal);
max-width: 400px;
@include ov-tablet-down {
max-width: none;
}
@include ov-mobile-down {
font-size: var(--ov-meet-font-size-sm);
}
}
}
.error-actions {
display: flex;
gap: var(--ov-meet-spacing-md);
margin-top: var(--ov-meet-spacing-md);
@include ov-tablet-down {
justify-content: center;
}
@include ov-mobile-down {
flex-direction: column;
align-items: center;
}
.action-button {
@include ov-button-base;
@include ov-flex-center;
gap: var(--ov-meet-spacing-xs);
min-width: 120px;
&.secondary {
color: var(--ov-meet-text-secondary);
border-color: var(--ov-meet-border-color);
&:hover {
color: var(--ov-meet-text-primary);
border-color: var(--ov-meet-text-primary);
background: var(--ov-meet-surface-hover);
}
}
mat-icon {
@include ov-icon(sm);
}
@include ov-mobile-down {
min-width: 200px;
}
}
}
}
.error-illustration {
flex: 1;
@include ov-flex-center;
@include ov-tablet-down {
order: -1;
}
.illustration-container {
@include ov-flex-center;
flex-direction: column;
gap: var(--ov-meet-spacing-lg);
opacity: 0.6;
.illustration-icon {
font-size: 120px;
width: 120px;
height: 120px;
color: var(--ov-meet-text-hint);
@include ov-mobile-down {
font-size: 80px;
width: 80px;
height: 80px;
}
}
.illustration-text {
display: flex;
flex-direction: column;
gap: var(--ov-meet-spacing-xs);
text-align: center;
.illustration-title {
font-size: var(--ov-meet-font-size-xl);
font-weight: var(--ov-meet-font-weight-bold);
color: var(--ov-meet-text-secondary);
@include ov-mobile-down {
font-size: var(--ov-meet-font-size-lg);
}
}
.illustration-subtitle {
font-size: var(--ov-meet-font-size-sm);
color: var(--ov-meet-text-hint);
}
}
}
}
}
} }
.card { // Animation states
width: 400px; .fade-in {
padding: 20px; animation: fadeIn 0.6s ease-out forwards;
text-align: center;
background-color: var(--ov-surface-color);
border-radius: var(--ov-surface-radius);
} }
mat-card-header { // Enhanced responsive design
justify-content: center; @include ov-mobile-down {
align-items: center; .error-page {
padding: var(--ov-meet-spacing-md);
.error-container {
.error-content {
.error-details {
.error-title {
margin-bottom: var(--ov-meet-spacing-md);
}
}
.error-actions {
margin-top: var(--ov-meet-spacing-lg);
.action-button {
padding: var(--ov-meet-spacing-md) var(--ov-meet-spacing-lg);
font-size: var(--ov-meet-font-size-md);
}
}
}
}
}
} }
mat-card-title { // Dark theme enhancements
font-size: 1.5em; [data-theme='dark'] {
} .error-page {
.error-container {
mat-card-content p { .error-content {
font-size: 1em; .error-icon-section {
color: #555; .error-main-icon {
} background: rgba(var(--ov-meet-color-error), 0.15);
}
mat-card-actions { }
margin-top: 20px; }
}
}
} }

View File

@ -1,12 +1,15 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { ActivatedRoute } from '@angular/router'; import { MatIconModule } from '@angular/material/icon';
import { ActivatedRoute, Router } from '@angular/router';
import { Location } from '@angular/common';
import { ErrorReason } from '@lib/models'; import { ErrorReason } from '@lib/models';
@Component({ @Component({
selector: 'ov-error', selector: 'ov-error',
standalone: true, standalone: true,
imports: [MatCardModule], imports: [MatCardModule, MatIconModule, MatButtonModule],
templateUrl: './error.component.html', templateUrl: './error.component.html',
styleUrl: './error.component.scss' styleUrl: './error.component.scss'
}) })
@ -14,7 +17,10 @@ export class ErrorComponent implements OnInit {
errorName = 'Error'; errorName = 'Error';
message = ''; message = '';
constructor(private route: ActivatedRoute) {} constructor(
private route: ActivatedRoute,
private location: Location
) {}
ngOnInit(): void { ngOnInit(): void {
this.route.queryParams.subscribe((params) => { this.route.queryParams.subscribe((params) => {
@ -64,4 +70,8 @@ export class ErrorComponent implements OnInit {
} }
}); });
} }
goBack(): void {
this.location.back();
}
} }