show error overlay for a backend error

This commit is contained in:
Chneemann 2024-08-08 00:28:58 +02:00
parent f00533de9d
commit 4870349e23
20 changed files with 126 additions and 106 deletions

View file

@ -25,9 +25,9 @@ class LoginView(APIView):
if user: if user:
if not user.is_active: if not user.is_active:
return Response({'password': 'Account is inactive, please check your mails'}, status=status.HTTP_403_FORBIDDEN) return Response({'error': 'Account is inactive, please check your mails'}, status=status.HTTP_403_FORBIDDEN)
return self._create_token_response(user) return self._create_token_response(user)
return Response({'mail': 'Unable to login with provided credentials.'}, status=status.HTTP_401_UNAUTHORIZED) return Response({'error': 'Unable to login with provided credentials.'}, status=status.HTTP_401_UNAUTHORIZED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@ -40,7 +40,7 @@ class RegisterView(APIView):
email = request.data.get('email') email = request.data.get('email')
password = request.data.get('password') password = request.data.get('password')
if CustomUser.objects.filter(email=email).exists(): if CustomUser.objects.filter(email=email).exists():
return Response({'mail': 'A user with this email already exists.'}, status=status.HTTP_400_BAD_REQUEST) return Response({'error': 'A user with this email already exists.'}, status=status.HTTP_400_BAD_REQUEST)
serialized = UserSerializer(data=request.data) serialized = UserSerializer(data=request.data)
if serialized.is_valid(): if serialized.is_valid():
@ -93,7 +93,7 @@ class ForgotPasswordView(APIView):
def post(self, request): def post(self, request):
email = request.data.get('email') email = request.data.get('email')
if not email: if not email:
return Response({"mail": "Email required."}, status=status.HTTP_400_BAD_REQUEST) return Response({"error": "Email required."}, status=status.HTTP_400_BAD_REQUEST)
try: try:
user = CustomUser.objects.get(email=email) user = CustomUser.objects.get(email=email)
@ -101,7 +101,7 @@ class ForgotPasswordView(APIView):
user.save() user.save()
self.send_email(user) self.send_email(user)
except CustomUser.DoesNotExist: except CustomUser.DoesNotExist:
return Response({"mail": "This email does not exist."}, status=status.HTTP_400_BAD_REQUEST) return Response({"error": "This email does not exist."}, status=status.HTTP_400_BAD_REQUEST)
return Response(status=status.HTTP_200_OK) return Response(status=status.HTTP_200_OK)
def send_email(self, user): def send_email(self, user):
@ -149,5 +149,5 @@ class AuthView(ObtainAuthToken):
def post(self, request): def post(self, request):
email = request.data.get('email') email = request.data.get('email')
if CustomUser.objects.filter(email=email).exists(): if CustomUser.objects.filter(email=email).exists():
return Response({'mail': 'A user with this email already exists.'}, status=status.HTTP_400_BAD_REQUEST) return Response({'error': 'A user with this email already exists.'}, status=status.HTTP_400_BAD_REQUEST)
return Response(status=status.HTTP_200_OK) return Response(status=status.HTTP_200_OK)

View file

@ -1,2 +1 @@
<!-- <router-outlet /> --> <router-outlet />
<app-error-toast></app-error-toast>

View file

@ -5,7 +5,7 @@ import { ErrorToastComponent } from './shared/components/error-toast/error-toast
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
standalone: true, standalone: true,
imports: [RouterOutlet, ErrorToastComponent], imports: [RouterOutlet],
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrl: './app.component.scss', styleUrl: './app.component.scss',
}) })

View file

@ -33,11 +33,11 @@
!isUserEmailValid(authData.mail.toLowerCase())) { !isUserEmailValid(authData.mail.toLowerCase())) {
<p>This is not a valid email format</p> <p>This is not a valid email format</p>
}} }}
<div *ngIf="errorService.error$ | async as errors"> <!-- <div *ngIf="errorService.error$ | async as errors">
<div *ngIf="errors?.['mail']"> <div *ngIf="errors?.['mail']">
<p>{{ errors["mail"] }}</p> <p>{{ errors["mail"] }}</p>
</div> </div>
</div> </div> -->
</div> </div>
<app-btn-large <app-btn-large
[value]="'Sign Up'" [value]="'Sign Up'"

View file

@ -5,6 +5,7 @@ import { Router } from '@angular/router';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { ErrorService } from '../../services/error.service'; import { ErrorService } from '../../services/error.service';
import { AuthService } from '../../services/auth.service'; import { AuthService } from '../../services/auth.service';
import { HttpErrorResponse } from '@angular/common/http';
@Component({ @Component({
selector: 'app-auth', selector: 'app-auth',
@ -46,7 +47,7 @@ export class AuthComponent {
this.router.navigate(['/register'], { queryParams }); this.router.navigate(['/register'], { queryParams });
this.errorService.clearError(); this.errorService.clearError();
} catch (error) { } catch (error) {
this.errorService.errorMsg(error); this.errorService.handleError(error);
} }
} }
} }

View file

@ -35,11 +35,11 @@
!isUserEmailValid(authData.mail.toLowerCase())) { !isUserEmailValid(authData.mail.toLowerCase())) {
<p>This is not a valid email format</p> <p>This is not a valid email format</p>
}} }}
<div *ngIf="errorService.error$ | async as errors"> <!-- <div *ngIf="errorService.error$ | async as errors">
<div *ngIf="errors?.['mail']"> <div *ngIf="errors?.['mail']">
<p>{{ errors["mail"] }}</p> <p>{{ errors["mail"] }}</p>
</div> </div>
</div> </div> -->
</div> </div>
<app-btn-large <app-btn-large
[value]="'Send Email'" [value]="'Send Email'"
@ -116,11 +116,11 @@
passwordConfirm.touched) { passwordConfirm.touched) {
<p>The passwords do not match</p> <p>The passwords do not match</p>
} }
<div *ngIf="errorService.error$ | async as errors"> <!-- <div *ngIf="errorService.error$ | async as errors">
<div *ngIf="errors?.['error']" class="error-msg"> <div *ngIf="errors?.['error']" class="error-msg">
<p>{{ errors["error"] }}</p> <p>{{ errors["error"] }}</p>
</div> </div>
</div> </div> -->
</div> </div>
<app-btn-large <app-btn-large
[value]="'Update Password'" [value]="'Update Password'"

View file

@ -76,7 +76,9 @@ export class ForgotPasswordComponent {
this.errorService.clearError(); this.errorService.clearError();
} catch (error) { } catch (error) {
this.sendMailSuccess = false; this.sendMailSuccess = false;
this.errorService.errorMsg(error); const errorMessage =
error instanceof Error ? error.message : 'An unknown error occurred';
this.errorService.errorMsg(errorMessage);
} }
} }
@ -94,7 +96,9 @@ export class ForgotPasswordComponent {
this.queryEmailSuccess = true; this.queryEmailSuccess = true;
this.errorService.clearError(); this.errorService.clearError();
} catch (error) { } catch (error) {
this.errorService.errorMsg(error); const errorMessage =
error instanceof Error ? error.message : 'An unknown error occurred';
this.errorService.errorMsg(errorMessage);
} }
} }
} }

View file

@ -31,11 +31,11 @@
<p>This is not a valid email format</p> <p>This is not a valid email format</p>
}} }}
<!-- Backend error handling --> <!-- Backend error handling -->
<div *ngIf="errorService.error$ | async as errors"> <!-- <div *ngIf="errorService.error$ | async as errors">
<div *ngIf="errors?.['mail']"> <div *ngIf="errors?.['mail']">
<p>{{ errors["mail"] }}</p> <p>{{ errors["mail"] }}</p>
</div> </div>
</div> </div> -->
</div> </div>
<input <input
type="password" type="password"
@ -61,11 +61,11 @@
<p>Password is too short, min 6 characters</p> <p>Password is too short, min 6 characters</p>
} } } }
<!-- Backend error handling --> <!-- Backend error handling -->
<div *ngIf="errorService.error$ | async as errors"> <!-- <div *ngIf="errorService.error$ | async as errors">
<div *ngIf="errors?.['password']"> <div *ngIf="errors?.['password']">
<p>{{ errors["password"] }}</p> <p>{{ errors["password"] }}</p>
</div> </div>
</div> </div> -->
</div> </div>
<div class="checkbox"> <div class="checkbox">
<label class="container" <label class="container"

View file

@ -45,7 +45,9 @@ export class LoginComponent {
this.router.navigate(['/browse/']); this.router.navigate(['/browse/']);
this.errorService.clearError(); this.errorService.clearError();
} catch (error) { } catch (error) {
this.errorService.errorMsg(error); const errorMessage =
error instanceof Error ? error.message : 'An unknown error occurred';
this.errorService.errorMsg(errorMessage);
} }
} }
} }

View file

@ -39,11 +39,11 @@
!isUserEmailValid(authData.mail.toLowerCase())) { !isUserEmailValid(authData.mail.toLowerCase())) {
<p>This is not a valid email format</p> <p>This is not a valid email format</p>
}} }}
<div *ngIf="errorService.error$ | async as errors"> <!-- <div *ngIf="errorService.error$ | async as errors">
<div *ngIf="errors?.['mail']" class="error-msg"> <div *ngIf="errors?.['mail']" class="error-msg">
<p>{{ errors["mail"] }}</p> <p>{{ errors["mail"] }}</p>
</div> </div>
</div> </div> -->
</div> </div>
<input <input
type="password" type="password"

View file

@ -55,7 +55,9 @@ export class RegisterComponent {
this.registrationSuccess = true; this.registrationSuccess = true;
this.errorService.clearError(); this.errorService.clearError();
} catch (error) { } catch (error) {
this.errorService.errorMsg(error); const errorMessage =
error instanceof Error ? error.message : 'An unknown error occurred';
this.errorService.errorMsg(errorMessage);
} }
} }
} }

View file

@ -12,10 +12,10 @@
</p> </p>
} }
</div> </div>
<div *ngIf="errorService.error$ | async as errors"> <!-- <div *ngIf="errorService.error$ | async as errors">
<div *ngIf="errors?.['error']" class="error-msg"> <div *ngIf="errors?.['error']" class="error-msg">
<p>{{ errors["error"] }}</p> <p>{{ errors["error"] }}</p>
</div> </div>
</div> </div> -->
</div> </div>
</section> </section>

View file

@ -45,20 +45,9 @@ export class VerifyEmailComponent {
this.errorService.clearError(); this.errorService.clearError();
} catch (error) { } catch (error) {
this.verified = false; this.verified = false;
this.errorMsg(error); const errorMessage =
} error instanceof Error ? error.message : 'An unknown error occurred';
} this.errorService.errorMsg(errorMessage);
errorMsg(error: any) {
if (error instanceof HttpErrorResponse) {
const errorTypes = ['error'];
for (const type of errorTypes) {
if (error.error[type]) {
this.errorService.setError(type, error.error[type]);
return;
}
}
this.errorService.clearError();
} }
} }
} }

View file

@ -8,4 +8,5 @@
></app-forgot-password> ></app-forgot-password>
<app-verify-email *ngIf="currentRoute === 'verify-email'"></app-verify-email> <app-verify-email *ngIf="currentRoute === 'verify-email'"></app-verify-email>
<app-footer></app-footer> <app-footer></app-footer>
<app-error-toast *ngIf="errorService.displayError"></app-error-toast>
</section> </section>

View file

@ -2,11 +2,3 @@ section {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
} }
// .bg-home {
// background-image: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)),
// url(./../../../assets/img/backgrounds/home.png);
// background-size: cover;
// background-position: center;
// background-repeat: no-repeat;
// }

View file

@ -9,6 +9,8 @@ import { LoginComponent } from '../auth/login/login.component';
import { ForgotPasswordComponent } from '../auth/forgot-password/forgot-password.component'; import { ForgotPasswordComponent } from '../auth/forgot-password/forgot-password.component';
import { BrowseComponent } from './browse/browse.component'; import { BrowseComponent } from './browse/browse.component';
import { VerifyEmailComponent } from '../auth/verify-email/verify-email.component'; import { VerifyEmailComponent } from '../auth/verify-email/verify-email.component';
import { ErrorToastComponent } from '../../shared/components/error-toast/error-toast.component';
import { ErrorService } from '../../services/error.service';
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
@ -23,14 +25,19 @@ import { VerifyEmailComponent } from '../auth/verify-email/verify-email.componen
ForgotPasswordComponent, ForgotPasswordComponent,
VerifyEmailComponent, VerifyEmailComponent,
BrowseComponent, BrowseComponent,
ErrorToastComponent,
], ],
templateUrl: './home.component.html', templateUrl: './home.component.html',
styleUrl: './home.component.scss', styleUrl: './home.component.scss',
}) })
export class HomeComponent implements OnInit { export class HomeComponent implements OnInit {
currentRoute: any; currentRoute: any;
displayErrorToast: boolean = false;
constructor(private route: ActivatedRoute) {} constructor(
private route: ActivatedRoute,
public errorService: ErrorService
) {}
ngOnInit(): void { ngOnInit(): void {
this.route.url.subscribe((url) => { this.route.url.subscribe((url) => {

View file

@ -1,49 +1,31 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators'; import { HttpErrorResponse } from '@angular/common/http';
export interface ErrorDetail {
type: string;
message: string;
}
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class ErrorService { export class ErrorService {
private errorSubject = new BehaviorSubject<ErrorDetail | null>(null); private errorSubject = new BehaviorSubject<string>('');
errorText$ = this.errorSubject.asObservable();
displayError = false;
error$: Observable<{ [key: string]: string } | null> = this.errorSubject errorMsg(message: string) {
.asObservable() this.errorSubject.next(message);
.pipe( this.displayError = true;
map((errorDetail) => {
if (errorDetail) {
return { [errorDetail.type]: errorDetail.message };
}
return null;
})
);
setError(type: string, message: string) {
const errorDetail: ErrorDetail = { type, message };
this.errorSubject.next(errorDetail);
} }
clearError() { clearError() {
this.errorSubject.next(null); this.errorSubject.next('');
this.displayError = false;
} }
errorMsg(error: any) { handleError(error: unknown) {
if (error instanceof HttpErrorResponse) { if (error instanceof HttpErrorResponse) {
const errorTypes = ['mail', 'password', 'error']; const errorMessage = error.error.error || 'An unknown error occurred';
for (const type of errorTypes) { this.errorMsg(errorMessage);
if (error.error[type]) { } else {
this.setError(type, error.error[type]); this.errorMsg('An unexpected error occurred');
return;
}
}
this.clearError();
} }
} }
} }

View file

@ -1,20 +1,21 @@
<section> <div class="overlay">
<div class="left"></div> <section class="slide-in-right">
<div class="content"> <div class="left"></div>
<img src="./../../../../assets/img/attention.svg" alt="" /> <div class="content">
<p> <img src="./../../../../assets/img/attention.svg" alt="" />
Lorem ipsum dolor sit amet consectetur adipisicing elit. Provident aliquam <p>
ex earum esse, totam placeat omnis sequi sint eveniet maiores explicabo {{ errorText }}
necessitatibus, est voluptatibus aperiam velit alias quae! Dicta aliquam </p>
aspernatur libero maiores tenetur aut ratione et esse nesciunt nostrum,
cupiditate recusandae perferendis? Sapiente reprehenderit nemo repellendus
libero adipisci explicabo!
</p>
</div>
<div class="right">
<div class="line"></div>
<div class="img">
<img src="./../../../../assets/img/close.svg" alt="" />
</div> </div>
</div> <div class="right">
</section> <div class="line"></div>
<div class="img">
<img
src="./../../../../assets/img/close.svg"
alt=""
(click)="closeError()"
/>
</div>
</div>
</section>
</div>

View file

@ -8,6 +8,16 @@ section {
border-radius: 20px; border-radius: 20px;
background-color: $purple; background-color: $purple;
box-shadow: 1px 1px 3px rgba($black, 0.5); box-shadow: 1px 1px 3px rgba($black, 0.5);
opacity: 0;
transform: translateY(100vh);
animation: slide-in-bottom 500ms forwards;
}
@keyframes slide-in-bottom {
to {
opacity: 1;
transform: translateY(0);
}
} }
.left { .left {
@ -26,6 +36,7 @@ section {
display: flex; display: flex;
align-items: center; align-items: center;
width: 82%; width: 82%;
min-height: 48px;
margin-left: 24px; margin-left: 24px;
p { p {
font-size: 18px; font-size: 18px;
@ -64,3 +75,19 @@ section {
} }
} }
} }
.overlay {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 100vw;
height: 100vh;
backdrop-filter: blur(5px);
background-color: rgba($color: #000000, $alpha: 0.2);
display: flex;
justify-content: center;
align-items: center;
z-index: 999;
}

View file

@ -1,4 +1,5 @@
import { Component, Input } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { ErrorService } from '../../../services/error.service';
@Component({ @Component({
selector: 'app-error-toast', selector: 'app-error-toast',
@ -7,6 +8,18 @@ import { Component, Input } from '@angular/core';
templateUrl: './error-toast.component.html', templateUrl: './error-toast.component.html',
styleUrl: './error-toast.component.scss', styleUrl: './error-toast.component.scss',
}) })
export class ErrorToastComponent { export class ErrorToastComponent implements OnInit {
@Input() errorText: string = ''; errorText: string = '';
constructor(private errorService: ErrorService) {}
ngOnInit(): void {
this.errorService.errorText$.subscribe((message: string) => {
this.errorText = message;
});
}
closeError() {
this.errorService.clearError();
}
} }