fixes
This commit is contained in:
@@ -28,6 +28,11 @@ export const routes: Routes = [
|
||||
loadComponent: () =>
|
||||
import('./features/auth/reset-password/reset-password').then((m) => m.ResetPassword),
|
||||
},
|
||||
{
|
||||
path: 'confirm-email/:hash',
|
||||
loadComponent: () =>
|
||||
import('./features/auth/confirm-email/confirm-email').then((m) => m.ConfirmEmail),
|
||||
},
|
||||
{
|
||||
path: 'password-change/:hash',
|
||||
loadComponent: () =>
|
||||
|
||||
@@ -69,6 +69,10 @@ export class AuthApi {
|
||||
return this.http.post<void>(`${this.baseUrl}/reset/password`, { hash, password });
|
||||
}
|
||||
|
||||
confirmEmail(hash: string): Observable<void> {
|
||||
return this.http.post<void>(`${this.baseUrl}/email/confirm`, { hash });
|
||||
}
|
||||
|
||||
createInvite(request: CreateInviteRequest): Observable<{ token: string }> {
|
||||
return this.http.post<{ token: string }>(`${this.baseUrl}/invite`, request);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<div class="auth-page">
|
||||
<mat-card class="auth-card">
|
||||
<mat-card-header><mat-card-title>E-Mail-Bestätigung</mat-card-title></mat-card-header>
|
||||
<mat-card-content>
|
||||
@if (confirming()) {
|
||||
<div class="state">
|
||||
<mat-spinner diameter="36" />
|
||||
<span>Deine E-Mail-Adresse wird bestätigt …</span>
|
||||
</div>
|
||||
} @else if (errorMessage()) {
|
||||
<p class="error">{{ errorMessage() }}</p>
|
||||
<a mat-flat-button routerLink="/auth/login">Zum Login</a>
|
||||
} @else {
|
||||
<p>Deine E-Mail-Adresse wurde erfolgreich bestätigt.</p>
|
||||
<a mat-flat-button routerLink="/auth/login">Zum Login</a>
|
||||
}
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
@@ -0,0 +1,18 @@
|
||||
.auth-page {
|
||||
min-height: 100dvh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.auth-card {
|
||||
width: min(100%, 420px);
|
||||
}
|
||||
.state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.error {
|
||||
color: var(--mat-sys-error);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
||||
import { ActivatedRoute, convertToParamMap, provideRouter } from '@angular/router';
|
||||
import { ConfirmEmail } from './confirm-email';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
|
||||
describe('ConfirmEmail', () => {
|
||||
let httpMock: HttpTestingController;
|
||||
|
||||
function setup(hash: string | null) {
|
||||
return TestBed.configureTestingModule({
|
||||
imports: [ConfirmEmail],
|
||||
providers: [
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting(),
|
||||
provideRouter([]),
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: { snapshot: { paramMap: convertToParamMap(hash ? { hash } : {}) } },
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
}
|
||||
|
||||
afterEach(() => httpMock.verify());
|
||||
|
||||
it('confirms the route hash and shows the success state', async () => {
|
||||
await setup('confirm-hash');
|
||||
httpMock = TestBed.inject(HttpTestingController);
|
||||
const fixture = TestBed.createComponent(ConfirmEmail);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance['confirming']()).toBe(true);
|
||||
|
||||
const request = httpMock.expectOne(`${environment.apiUrl}auth/email/confirm`);
|
||||
expect(request.request.body).toEqual({ hash: 'confirm-hash' });
|
||||
request.flush(null);
|
||||
|
||||
expect(fixture.componentInstance['confirming']()).toBe(false);
|
||||
expect(fixture.componentInstance['errorMessage']()).toBeNull();
|
||||
});
|
||||
|
||||
it('shows an error state when the hash is invalid or expired', async () => {
|
||||
await setup('bad-hash');
|
||||
httpMock = TestBed.inject(HttpTestingController);
|
||||
const fixture = TestBed.createComponent(ConfirmEmail);
|
||||
fixture.detectChanges();
|
||||
|
||||
const request = httpMock.expectOne(`${environment.apiUrl}auth/email/confirm`);
|
||||
request.flush(null, { status: 404, statusText: 'Not Found' });
|
||||
|
||||
expect(fixture.componentInstance['confirming']()).toBe(false);
|
||||
expect(fixture.componentInstance['errorMessage']()).toBe(
|
||||
'Der Bestätigungslink ist ungültig oder abgelaufen.',
|
||||
);
|
||||
});
|
||||
|
||||
it('shows an error state without making a request when the hash is missing', async () => {
|
||||
await setup(null);
|
||||
httpMock = TestBed.inject(HttpTestingController);
|
||||
const fixture = TestBed.createComponent(ConfirmEmail);
|
||||
fixture.detectChanges();
|
||||
|
||||
httpMock.expectNone(`${environment.apiUrl}auth/email/confirm`);
|
||||
expect(fixture.componentInstance['confirming']()).toBe(false);
|
||||
expect(fixture.componentInstance['errorMessage']()).toBe('Der Bestätigungslink ist unvollständig.');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Component, inject, signal } from '@angular/core';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { AuthApi } from '../../../core/auth/auth-api';
|
||||
|
||||
@Component({
|
||||
selector: 'app-confirm-email',
|
||||
imports: [RouterLink, MatButtonModule, MatCardModule, MatProgressSpinnerModule],
|
||||
templateUrl: './confirm-email.html',
|
||||
styleUrl: './confirm-email.scss',
|
||||
})
|
||||
export class ConfirmEmail {
|
||||
private readonly authApi = inject(AuthApi);
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
private readonly hash = this.route.snapshot.paramMap.get('hash');
|
||||
|
||||
protected readonly confirming = signal(true);
|
||||
protected readonly errorMessage = signal<string | null>(null);
|
||||
|
||||
constructor() {
|
||||
if (!this.hash) {
|
||||
this.confirming.set(false);
|
||||
this.errorMessage.set('Der Bestätigungslink ist unvollständig.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.authApi.confirmEmail(this.hash).subscribe({
|
||||
next: () => this.confirming.set(false),
|
||||
error: () => {
|
||||
this.confirming.set(false);
|
||||
this.errorMessage.set('Der Bestätigungslink ist ungültig oder abgelaufen.');
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user