verifizierung
This commit is contained in:
20
listify-client/package-lock.json
generated
20
listify-client/package-lock.json
generated
@@ -1143,22 +1143,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.0.tgz",
|
||||
"integrity": "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.1",
|
||||
"@emnapi/wasi-threads": "1.2.2",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.0.tgz",
|
||||
"integrity": "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -1168,9 +1168,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
|
||||
"integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
mat-icon-button
|
||||
type="button"
|
||||
class="menu-button"
|
||||
aria-label="Menue oeffnen"
|
||||
aria-label="Menü öffnen"
|
||||
(click)="toggleSidebar()"
|
||||
>
|
||||
<mat-icon aria-hidden="true">menu</mat-icon>
|
||||
|
||||
@@ -34,6 +34,16 @@
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.auth-card mat-card-actions {
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.auth-card mat-card-actions mat-progress-spinner {
|
||||
display: inline-flex;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.verify-card mat-card-content {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ export interface VerifyEmailResponse {
|
||||
user: PublicUser;
|
||||
}
|
||||
|
||||
export interface ResendVerificationResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface LoginRequest {
|
||||
email: string;
|
||||
password: string;
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
PublicUser,
|
||||
RegisterRequest,
|
||||
RegisterResponse,
|
||||
ResendVerificationResponse,
|
||||
VerifyEmailResponse,
|
||||
} from './auth.models';
|
||||
|
||||
@@ -39,6 +40,13 @@ export class AuthService {
|
||||
return this.http.get<VerifyEmailResponse>(`${this.apiUrl}/verify-email`, { params });
|
||||
}
|
||||
|
||||
resendVerificationEmail(email: string): Observable<ResendVerificationResponse> {
|
||||
return this.http.post<ResendVerificationResponse>(
|
||||
`${this.apiUrl}/resend-verification`,
|
||||
{ email },
|
||||
);
|
||||
}
|
||||
|
||||
loadCurrentUser(): Observable<PublicUser> {
|
||||
return this.http
|
||||
.get<PublicUser>(`${this.apiUrl}/me`)
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
@if (form.controls.email.hasError('required')) {
|
||||
<mat-error>E-Mail ist erforderlich.</mat-error>
|
||||
} @else if (form.controls.email.hasError('email')) {
|
||||
<mat-error>Bitte gib eine gueltige E-Mail ein.</mat-error>
|
||||
<mat-error>Bitte gib eine gültige E-Mail ein.</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
@@ -54,6 +54,19 @@
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions align="end">
|
||||
<button
|
||||
mat-button
|
||||
type="button"
|
||||
[disabled]="resendingVerification || loading"
|
||||
(click)="resendVerificationEmail()"
|
||||
>
|
||||
@if (resendingVerification) {
|
||||
<mat-progress-spinner mode="indeterminate" diameter="18" />
|
||||
} @else {
|
||||
<mat-icon aria-hidden="true">mark_email_unread</mat-icon>
|
||||
}
|
||||
Verifizierungsmail erneut senden
|
||||
</button>
|
||||
<a mat-button routerLink="/register">Konto erstellen</a>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
|
||||
@@ -41,6 +41,7 @@ export class LoginComponent {
|
||||
password: ['', [Validators.required, Validators.minLength(8)]],
|
||||
});
|
||||
protected loading = false;
|
||||
protected resendingVerification = false;
|
||||
protected hidePassword = true;
|
||||
|
||||
submit(): void {
|
||||
@@ -65,4 +66,26 @@ export class LoginComponent {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
resendVerificationEmail(): void {
|
||||
const emailControl = this.form.controls.email;
|
||||
|
||||
if (emailControl.invalid) {
|
||||
emailControl.markAsTouched();
|
||||
return;
|
||||
}
|
||||
|
||||
this.resendingVerification = true;
|
||||
this.auth
|
||||
.resendVerificationEmail(emailControl.value)
|
||||
.pipe(finalize(() => (this.resendingVerification = false)))
|
||||
.subscribe({
|
||||
next: (response) => {
|
||||
this.snackBar.open(response.message, 'OK', { duration: 6000 });
|
||||
},
|
||||
error: (error: unknown) => {
|
||||
this.snackBar.open(getAuthErrorMessage(error), 'OK', { duration: 5000 });
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
@if (form.controls.email.hasError('required')) {
|
||||
<mat-error>E-Mail ist erforderlich.</mat-error>
|
||||
} @else if (form.controls.email.hasError('email')) {
|
||||
<mat-error>Bitte gib eine gueltige E-Mail ein.</mat-error>
|
||||
<mat-error>Bitte gib eine gültige E-Mail ein.</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<mat-card class="auth-card verify-card" appearance="outlined">
|
||||
<mat-card-header>
|
||||
<mat-card-title>E-Mail-Verifikation</mat-card-title>
|
||||
<mat-card-subtitle>{{ email() || 'Listify-Konto bestaetigen' }}</mat-card-subtitle>
|
||||
<mat-card-subtitle>{{ email() || 'Listify-Konto bestätigen' }}</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
|
||||
@@ -26,7 +26,7 @@ export class VerifyEmailComponent implements OnInit {
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
|
||||
protected readonly state = signal<VerificationState>('loading');
|
||||
protected readonly message = signal('E-Mail wird bestaetigt.');
|
||||
protected readonly message = signal('E-Mail wird bestätigt.');
|
||||
protected readonly email = signal<string | null>(null);
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -34,7 +34,7 @@ export class VerifyEmailComponent implements OnInit {
|
||||
|
||||
if (!token) {
|
||||
this.state.set('missing-token');
|
||||
this.message.set('Der Verifikationslink enthaelt keinen Token.');
|
||||
this.message.set('Der Verifikationslink enthält keinen Token.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="list-detail-page">
|
||||
<header class="detail-header">
|
||||
<button mat-icon-button type="button" aria-label="Zurueck" (click)="backToLists()">
|
||||
<button mat-icon-button type="button" aria-label="Zurück" (click)="backToLists()">
|
||||
<mat-icon aria-hidden="true">arrow_back</mat-icon>
|
||||
</button>
|
||||
<div>
|
||||
@@ -70,9 +70,9 @@
|
||||
<mat-card-title>Items</mat-card-title>
|
||||
<mat-card-subtitle>
|
||||
@if (canEditItems()) {
|
||||
{{ list()?.items?.length || 0 }} Eintraege
|
||||
{{ list()?.items?.length || 0 }} Einträge
|
||||
} @else {
|
||||
Nach dem Speichern verfuegbar
|
||||
Nach dem Speichern verfügbar
|
||||
}
|
||||
</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
@@ -95,14 +95,14 @@
|
||||
} @else {
|
||||
<mat-icon aria-hidden="true">add</mat-icon>
|
||||
}
|
||||
Hinzufuegen
|
||||
Hinzufügen
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@if (!canEditItems()) {
|
||||
<div class="inline-empty">
|
||||
<mat-icon aria-hidden="true">save</mat-icon>
|
||||
<span>Speichere die Liste, bevor du Items hinzufuegst.</span>
|
||||
<span>Speichere die Liste, bevor du Items hinzufügst.</span>
|
||||
</div>
|
||||
} @else if (list()?.items?.length) {
|
||||
<ul class="check-items">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<header class="page-header">
|
||||
<div>
|
||||
<h1>Listen</h1>
|
||||
<p>Deine persoenlichen Listify-Listen.</p>
|
||||
<p>Deine persönlichen Listify-Listen.</p>
|
||||
</div>
|
||||
|
||||
<a mat-flat-button routerLink="/lists/new">
|
||||
@@ -46,7 +46,7 @@
|
||||
mat-icon-button
|
||||
matSuffix
|
||||
type="button"
|
||||
aria-label="Suche loeschen"
|
||||
aria-label="Suche löschen"
|
||||
(click)="searchTerm.set('')"
|
||||
>
|
||||
<mat-icon aria-hidden="true">close</mat-icon>
|
||||
@@ -94,7 +94,7 @@
|
||||
@if (activeFilterCount() > 0 || sortOption() !== 'updated-desc') {
|
||||
<button mat-button type="button" (click)="resetFilters()">
|
||||
<mat-icon aria-hidden="true">restart_alt</mat-icon>
|
||||
Zuruecksetzen
|
||||
Zurücksetzen
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@@ -108,7 +108,7 @@
|
||||
<p>Mit den aktuellen Filtern wurde keine Liste gefunden.</p>
|
||||
<button mat-stroked-button type="button" (click)="resetFilters()">
|
||||
<mat-icon aria-hidden="true">restart_alt</mat-icon>
|
||||
Filter zuruecksetzen
|
||||
Filter zurücksetzen
|
||||
</button>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
@@ -164,7 +164,7 @@
|
||||
[attr.data-onboarding]="onboarding.isListOpenTarget(list.id) ? 'open-list' : null"
|
||||
>
|
||||
<mat-icon aria-hidden="true">open_in_new</mat-icon>
|
||||
Oeffnen
|
||||
Öffnen
|
||||
</a>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
>
|
||||
<div class="step-line">
|
||||
<span>Schritt {{ onboarding.currentStepNumber() }} von {{ onboarding.totalSteps }}</span>
|
||||
<button mat-icon-button type="button" aria-label="Onboarding schliessen" (click)="onboarding.skip()">
|
||||
<button mat-icon-button type="button" aria-label="Onboarding schließen" (click)="onboarding.skip()">
|
||||
<mat-icon aria-hidden="true">close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
@@ -34,7 +34,7 @@
|
||||
Fertig
|
||||
</button>
|
||||
} @else {
|
||||
<button mat-button type="button" (click)="onboarding.skip()">Ueberspringen</button>
|
||||
<button mat-button type="button" (click)="onboarding.skip()">Überspringen</button>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -34,12 +34,12 @@ const STEPS: Record<OnboardingStepKey, OnboardingStep> = {
|
||||
'template-details': {
|
||||
key: 'template-details',
|
||||
title: 'Template benennen',
|
||||
body: 'Gib deinem Template einen Titel und speichere es. Danach kannst du Items hinzufuegen.',
|
||||
body: 'Gib deinem Template einen Titel und speichere es. Danach kannst du Items hinzufügen.',
|
||||
targetSelector: '[data-onboarding="template-details"]',
|
||||
},
|
||||
'template-item': {
|
||||
key: 'template-item',
|
||||
title: 'Erstes Item hinzufuegen',
|
||||
title: 'Erstes Item hinzufügen',
|
||||
body: 'Trage ein Item ein und fuege es hinzu. Items sind spaeter die Punkte deiner Liste.',
|
||||
targetSelector: '[data-onboarding="template-item"]',
|
||||
},
|
||||
@@ -51,7 +51,7 @@ const STEPS: Record<OnboardingStepKey, OnboardingStep> = {
|
||||
},
|
||||
'list-open': {
|
||||
key: 'list-open',
|
||||
title: 'Liste oeffnen',
|
||||
title: 'Liste öffnen',
|
||||
body: 'Oeffne die neu erstellte Liste. Dort kannst du die Items abhaken.',
|
||||
targetSelector: '[data-onboarding="open-list"]',
|
||||
},
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<mat-icon aria-hidden="true">delete_forever</mat-icon>
|
||||
</div>
|
||||
|
||||
<h2 mat-dialog-title>Template loeschen?</h2>
|
||||
<h2 mat-dialog-title>Template löschen?</h2>
|
||||
|
||||
<mat-dialog-content>
|
||||
<p>
|
||||
<strong>{{ data.templateName }}</strong> wird dauerhaft geloescht. Bereits daraus erstellte
|
||||
<strong>{{ data.templateName }}</strong> wird dauerhaft gelöscht. Bereits daraus erstellte
|
||||
Listen bleiben erhalten.
|
||||
</p>
|
||||
</mat-dialog-content>
|
||||
@@ -15,6 +15,6 @@
|
||||
<button mat-button type="button" mat-dialog-close>Abbrechen</button>
|
||||
<button mat-flat-button type="button" color="warn" [mat-dialog-close]="true">
|
||||
<mat-icon aria-hidden="true">delete</mat-icon>
|
||||
Loeschen
|
||||
Löschen
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="template-detail-page">
|
||||
<header class="detail-header">
|
||||
<button mat-icon-button type="button" aria-label="Zurueck" (click)="backToTemplates()">
|
||||
<button mat-icon-button type="button" aria-label="Zurück" (click)="backToTemplates()">
|
||||
<mat-icon aria-hidden="true">arrow_back</mat-icon>
|
||||
</button>
|
||||
<div>
|
||||
@@ -23,7 +23,7 @@
|
||||
}
|
||||
Als Liste
|
||||
</button>
|
||||
<button mat-icon-button type="button" aria-label="Template loeschen" [disabled]="deletingTemplate()" (click)="deleteTemplate()">
|
||||
<button mat-icon-button type="button" aria-label="Template löschen" [disabled]="deletingTemplate()" (click)="deleteTemplate()">
|
||||
@if (deletingTemplate()) {
|
||||
<mat-progress-spinner mode="indeterminate" diameter="18" />
|
||||
} @else {
|
||||
@@ -91,12 +91,12 @@
|
||||
<mat-card-title>Items</mat-card-title>
|
||||
<mat-card-subtitle>
|
||||
@if (canEditItems()) {
|
||||
{{ template()?.items?.length || 0 }} Eintraege
|
||||
{{ template()?.items?.length || 0 }} Einträge
|
||||
@if (reordering()) {
|
||||
- Reihenfolge wird gespeichert
|
||||
}
|
||||
} @else {
|
||||
Nach dem Speichern verfuegbar
|
||||
Nach dem Speichern verfügbar
|
||||
}
|
||||
</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
@@ -119,14 +119,14 @@
|
||||
} @else {
|
||||
<mat-icon aria-hidden="true">add</mat-icon>
|
||||
}
|
||||
Hinzufuegen
|
||||
Hinzufügen
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@if (!canEditItems()) {
|
||||
<div class="inline-empty">
|
||||
<mat-icon aria-hidden="true">save</mat-icon>
|
||||
<span>Speichere das Template, bevor du Items hinzufuegst.</span>
|
||||
<span>Speichere das Template, bevor du Items hinzufügst.</span>
|
||||
</div>
|
||||
} @else if (template()?.items?.length) {
|
||||
<ul
|
||||
|
||||
@@ -297,7 +297,7 @@ export class TemplateDetailComponent implements OnInit {
|
||||
.pipe(finalize(() => this.deletingTemplate.set(false)))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.snackBar.open('Template geloescht.', 'OK', { duration: 3000 });
|
||||
this.snackBar.open('Template gelöscht.', 'OK', { duration: 3000 });
|
||||
void this.router.navigateByUrl('/templates');
|
||||
},
|
||||
error: (error: unknown) => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<header class="page-header">
|
||||
<div>
|
||||
<h1>Templates</h1>
|
||||
<p>Vorlagen fuer wiederkehrende Listen.</p>
|
||||
<p>Vorlagen für wiederkehrende Listen.</p>
|
||||
</div>
|
||||
|
||||
<a
|
||||
@@ -52,7 +52,7 @@
|
||||
<div class="template-meta">
|
||||
<span>
|
||||
<mat-icon aria-hidden="true">checklist</mat-icon>
|
||||
{{ template.items.length }} Eintraege
|
||||
{{ template.items.length }} Einträge
|
||||
</span>
|
||||
<span>
|
||||
<mat-icon aria-hidden="true">schedule</mat-icon>
|
||||
@@ -95,7 +95,7 @@
|
||||
<button
|
||||
mat-icon-button
|
||||
type="button"
|
||||
[attr.aria-label]="template.name + ' loeschen'"
|
||||
[attr.aria-label]="template.name + ' löschen'"
|
||||
[disabled]="deletingTemplateId() === template.id"
|
||||
(click)="deleteTemplate(template)"
|
||||
>
|
||||
|
||||
@@ -131,7 +131,7 @@ export class TemplatesComponent implements OnInit {
|
||||
(existingTemplate) => existingTemplate.id !== template.id,
|
||||
),
|
||||
);
|
||||
this.snackBar.open('Template geloescht.', 'OK', { duration: 3000 });
|
||||
this.snackBar.open('Template gelöscht.', 'OK', { duration: 3000 });
|
||||
},
|
||||
error: (error: unknown) => {
|
||||
this.snackBar.open(getAuthErrorMessage(error), 'OK', {
|
||||
|
||||
Reference in New Issue
Block a user