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