Started implementing user settings -> security -> e-mail management

This commit is contained in:
2026-04-18 19:28:51 +02:00
parent 4eaaacac2c
commit 20e67ecd85
9 changed files with 254 additions and 53 deletions

View File

@@ -0,0 +1,97 @@
<div class="option">
<ng-template [(tuiDialog)]="changeEmailDialogOpen"
[tuiDialogOptions]="{label: ('chat.userSettingsDialog.security.changeEmailDialog.'+ (changeEmailRemoveMode() ? 'labelRemove' : serviceManager.currentSession()!.userData.emailSet ? 'label' : 'labelSet'))|translate}">
@if (step() == 0) {
<form [formGroup]="changeEmailForm" style="display: flex; flex-direction: column; gap: 10px">
@if (!changeEmailRemoveMode()) {
<tui-textfield iconStart="@tui.mail">
<label
tuiLabel>{{ "chat.userSettingsDialog.security.changeEmailDialog.newMail"|translate }}</label>
<input formControlName="currentPassword" tuiInput type="email">
</tui-textfield>
}
<tui-textfield iconStart="@tui.key">
<label tuiLabel>{{ "chat.userSettingsDialog.security.changeEmailDialog.currentPassword"|translate }}</label>
<input formControlName="currentPassword" tuiInput type="password">
</tui-textfield>
@if (changeEmailForm.controls['currentPassword'].dirty) {
@if (changeEmailForm.controls['currentPassword'].hasError("required")) {
<tui-error
[error]="'chat.userSettingsDialog.security.changeEmailDialog.errors.currentPasswordRequired'|translate"></tui-error>
}
@if (changeEmailForm.controls['currentPassword'].hasError("incorrect")) {
<tui-error
[error]="'chat.userSettingsDialog.security.changeEmailDialog.errors.incorrectPassword'|translate"></tui-error>
}
}
</form>
<footer>
<button tuiButton iconStart="@tui.check" [loading]="changeEmailPending()"
[disabled]="changeEmailPending()"
(click)="changeEmail(changeEmailForm.controls['newAddress'].value, changeEmailForm.controls['currentPassword'].value)">
{{ ('chat.userSettingsDialog.security.changeEmailDialog.' + (changeEmailRemoveMode() ? 'labelRemove' : serviceManager.currentSession()!.userData.passwordSet ? 'label' : 'labelSet'))|translate }}
</button>
</footer>
} @else {
<form style="display: flex; flex-direction: column; gap: 10px" [formGroup]="verifyEmailForm">
@if (!changeEmailRemoveMode()) {
<tui-textfield iconStart="@tui.mail">
<label tuiLabel>{{"chat.userSettingsDialog.security.changeEmailDialog.oldCode"|translate}}</label>
<input tuiInput type="number" [step]="1" formControlName="oldCode">
</tui-textfield>
}
<tui-textfield iconStart="@tui.badge-plus">
<label tuiLabel>{{"chat.userSettingsDialog.security.changeEmailDialog.newCode"|translate}}</label>
<input tuiInput type="number" [step]="1" formControlName="newCode">
</tui-textfield>
</form>
<footer>
<button tuiButton iconStart="@tui.check" [loading]="verifyEmailPending()"
[disabled]="verifyEmailForm.invalid || verifyEmailPending()"
(click)="verifyEmail(verifyEmailForm.controls['newCode'].value, verifyEmailForm.controls['oldCode'].value)">
{{ ('chat.userSettingsDialog.security.changeEmailDialog.' + (changeEmailRemoveMode() ? 'labelRemove' : serviceManager.currentSession()!.userData.passwordSet ? 'label' : 'labelSet'))|translate }}
</button>
</footer>
}
</ng-template>
<header>
<tui-icon icon="@tui.mail"/>
<span>{{ "chat.userSettingsDialog.security.email"|translate }}</span>
@if (serviceManager.currentSession()!.userData.emailSet) {
<div tuiBadge appearance="positive"
iconStart="@tui.check">{{ 'chat.userSettingsDialog.security.set'|translate }}
</div>
} @else {
<div tuiBadge appearance="negative"
iconStart="@tui.x">{{ 'chat.userSettingsDialog.security.notSet'|translate }}
</div>
}
</header>
<main>
<button tuiButton appearance="outline" (click)="openChangeEmailDialog(false)"
[iconStart]="serviceManager.currentSession()!.userData.emailSet ? '@tui.pencil' : '@tui.plus'">
@if (serviceManager.currentSession()!.userData.phoneSet) {
{{ "chat.userSettingsDialog.security.changeMail"|translate }}
} @else {
{{ "chat.userSettingsDialog.security.setMail"|translate }}
}
</button>
@if (serviceManager.currentSession()!.userData.emailSet) {
<button tuiButton appearance="outline" tuiAppearanceMode="invalid" iconStart="@tui.x"
(click)="openChangeEmailDialog(true)"
style="color: var(--tui-text-negative)">
{{ "chat.userSettingsDialog.security.removeMail"|translate }}
</button>
}
</main>
</div>

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Email } from './email';
describe('Email', () => {
let component: Email;
let fixture: ComponentFixture<Email>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [Email],
}).compileComponents();
fixture = TestBed.createComponent(Email);
component = fixture.componentInstance;
await fixture.whenStable();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,105 @@
import {Component, inject, signal} from '@angular/core';
import {TranslatePipe} from '@ngx-translate/core';
import {TuiBadge, TuiButtonLoading, TuiInputNumber} from '@taiga-ui/kit';
import {
TuiButton,
TuiDialog,
TuiErrorComponent,
TuiIcon,
TuiInputDirective,
TuiLabel,
TuiTextfieldComponent
} from '@taiga-ui/core';
import {ServiceManager} from '../../../../service-manager';
import {
AbstractControl,
FormControl,
FormGroup,
ReactiveFormsModule,
ValidationErrors,
Validators
} from '@angular/forms';
@Component({
selector: 'user-settings-security-email',
imports: [
TranslatePipe,
TuiBadge,
TuiButton,
TuiIcon,
ReactiveFormsModule,
TuiButtonLoading,
TuiDialog,
TuiErrorComponent,
TuiInputDirective,
TuiLabel,
TuiTextfieldComponent,
TuiInputNumber,
],
templateUrl: './email.html',
styleUrl: './email.scss',
})
export class Email {
serviceManager = inject(ServiceManager)
step = signal(0)
changeEmailDialogOpen = signal(false)
changeEmailRemoveMode = signal(false)
changeEmailPending = signal(false)
verifyEmailPending = signal(false)
newAddress = ""
changeEmailForm = new FormGroup({
newAddress: new FormControl(""),
currentPassword: new FormControl(""),
})
verifyEmailForm = new FormGroup({
oldCode: new FormControl(0),
newCode: new FormControl(0, {validators: [Validators.required]})
})
openChangeEmailDialog(modeRemove: boolean) {
this.changeEmailDialogOpen.set(true)
this.changeEmailRemoveMode.set(modeRemove)
this.changeEmailForm.controls["currentPassword"].clearValidators()
this.changeEmailForm.controls["newAddress"].clearValidators()
if (!modeRemove) {
this.changeEmailForm.controls["newAddress"].setValidators([Validators.required])
this.changeEmailForm.controls["newAddress"].updateValueAndValidity()
}
this.changeEmailForm.controls["currentPassword"].setValidators([Validators.required])
this.changeEmailForm.controls["currentPassword"].updateValueAndValidity()
}
async changeEmail(currentPassword: string | null, newMail: string | null) {
this.changeEmailPending.set(true)
const service = this.serviceManager.currentSessionHandler
if (service) {
try {
await service.changeEmail(newMail ?? "", currentPassword ?? "")
if (!this.changeEmailRemoveMode()) {
this.newAddress = newMail ?? ""
this.verifyEmailForm.controls["oldCode"].setValidators([Validators.required])
this.step.set(1)
}
} catch (e) {
this.changeEmailForm.controls["currentPassword"].setErrors({incorrect: true})
}
}
}
async verifyEmail(newCode: number | null, oldCode: number | null) {
this.verifyEmailPending.set(true)
const service = this.serviceManager.currentSessionHandler
if (service) {
try {
await service.verifyEmailChange(oldCode ?? 0, newCode ?? 0, this.newAddress)
this.changeEmailDialogOpen.set(false)
} catch (e) {
this.verifyEmailForm.controls["newCode"].setErrors({incorrect: true})
}
}
}
}

View File

@@ -1,7 +1,7 @@
<div class="option">
<ng-template [(tuiDialog)]="changePasswordDialogOpen"
[tuiDialogOptions]="{label: ('chat.userSettingsDialog.security.changePasswordDialog.'+ (changePasswordRemoveMode() ? 'labelRemove' : serviceManager.currentSession()!.userData.passwordSet ? 'label' : 'labelSet'))|translate}">
<form [formGroup]="changePasswordForm" style="display: flex; flex-direction: column; gap: 10px">
<form [formGroup]="changeEmailForm" style="display: flex; flex-direction: column; gap: 10px">
@if (serviceManager.currentSession()!.userData.passwordSet) {
<tui-textfield iconStart="@tui.key">
<label
@@ -9,13 +9,13 @@
<input formControlName="currentPassword" tuiInput type="password">
</tui-textfield>
@if (changePasswordForm.controls['currentPassword'].dirty) {
@if (changePasswordForm.controls['currentPassword'].hasError("required")) {
@if (changeEmailForm.controls['currentPassword'].dirty) {
@if (changeEmailForm.controls['currentPassword'].hasError("required")) {
<tui-error
[error]="'chat.userSettingsDialog.security.changePasswordDialog.errors.currentPasswordRequired'|translate"></tui-error>
}
@if (changePasswordForm.controls['currentPassword'].hasError("incorrect")) {
@if (changeEmailForm.controls['currentPassword'].hasError("incorrect")) {
<tui-error
[error]="'chat.userSettingsDialog.security.changePasswordDialog.errors.incorrectPassword'|translate"></tui-error>
}
@@ -38,8 +38,8 @@
<footer>
<button tuiButton iconStart="@tui.check" [loading]="changePasswordPending()"
[disabled]="changePasswordForm.invalid || changePasswordPending()"
(click)="changePassword(changePasswordForm.controls['currentPassword'].value, changePasswordForm.controls['newPassword'].value)">
[disabled]="changeEmailForm.invalid || changePasswordPending()"
(click)="changePassword(changeEmailForm.controls['currentPassword'].value, changeEmailForm.controls['newPassword'].value)">
{{ ('chat.userSettingsDialog.security.changePasswordDialog.' + (changePasswordRemoveMode() ? 'labelRemove' : serviceManager.currentSession()!.userData.passwordSet ? 'label' : 'labelSet'))|translate }}
</button>
</footer>

View File

@@ -47,7 +47,7 @@ export class Password {
changePasswordRemoveMode = signal(false)
changePasswordPending = signal(false)
changePasswordForm = new FormGroup({
changeEmailForm = new FormGroup({
currentPassword: new FormControl(""),
newPassword: new FormControl(""),
newPasswordRepeat: new FormControl("")
@@ -58,18 +58,18 @@ export class Password {
openChangePasswordDialog(modeRemove: boolean) {
this.changePasswordDialogOpen.set(true)
this.changePasswordRemoveMode.set(modeRemove)
this.changePasswordForm.controls["currentPassword"].clearValidators()
this.changePasswordForm.controls["newPassword"].clearValidators()
this.changePasswordForm.controls["newPasswordRepeat"].clearValidators()
this.changeEmailForm.controls["currentPassword"].clearValidators()
this.changeEmailForm.controls["newPassword"].clearValidators()
this.changeEmailForm.controls["newPasswordRepeat"].clearValidators()
if (!modeRemove) {
this.changePasswordForm.controls["newPassword"].setValidators([Validators.required])
this.changePasswordForm.controls["newPasswordRepeat"].setValidators([Validators.required])
this.changePasswordForm.controls["newPassword"].updateValueAndValidity()
this.changePasswordForm.controls["newPasswordRepeat"].updateValueAndValidity()
this.changeEmailForm.controls["newPassword"].setValidators([Validators.required])
this.changeEmailForm.controls["newPasswordRepeat"].setValidators([Validators.required])
this.changeEmailForm.controls["newPassword"].updateValueAndValidity()
this.changeEmailForm.controls["newPasswordRepeat"].updateValueAndValidity()
}
this.changePasswordForm.controls["currentPassword"].setValidators([Validators.required])
this.changePasswordForm.controls["currentPassword"].updateValueAndValidity()
this.changeEmailForm.controls["currentPassword"].setValidators([Validators.required])
this.changeEmailForm.controls["currentPassword"].updateValueAndValidity()
}
async changePassword(currentPassword: string | null, newPassword: string | null) {
@@ -79,7 +79,7 @@ export class Password {
try {
await service.changePassword(newPassword ?? "", currentPassword ?? "")
} catch (e) {
this.changePasswordForm.controls["currentPassword"].setErrors({incorrect: true})
this.changeEmailForm.controls["currentPassword"].setErrors({incorrect: true})
}
}
}

View File

@@ -4,41 +4,7 @@
</p>
<div id="options" tuiGroup orientation="vertical">
<user-settings-security-password/>
<div class="option">
<header>
<tui-icon icon="@tui.mail"/>
<span>{{ "chat.userSettingsDialog.security.email"|translate }}</span>
@if (serviceManager.currentSession()!.userData.phoneSet) {
<div tuiBadge appearance="positive"
iconStart="@tui.check">{{ 'chat.userSettingsDialog.security.set'|translate }}
</div>
} @else {
<div tuiBadge appearance="negative"
iconStart="@tui.x">{{ 'chat.userSettingsDialog.security.notSet'|translate }}
</div>
}
</header>
<main>
<button tuiButton appearance="outline"
[iconStart]="serviceManager.currentSession()!.userData.emailSet ? '@tui.pencil' : '@tui.plus'">
@if (serviceManager.currentSession()!.userData.phoneSet) {
{{ "chat.userSettingsDialog.security.changeMail"|translate }}
} @else {
{{ "chat.userSettingsDialog.security.setMail"|translate }}
}
</button>
@if (serviceManager.currentSession()!.userData.emailSet) {
<button tuiButton appearance="outline" tuiAppearanceMode="invalid" iconStart="@tui.x"
style="color: var(--tui-text-negative)">
{{ "chat.userSettingsDialog.security.removeMail"|translate }}
</button>
}
</main>
</div>
<user-settings-security-email/>
<div class="option">
<header>
<tui-icon icon="@tui.phone"/>

View File

@@ -13,6 +13,7 @@ import {
Validators
} from '@angular/forms';
import {Password} from './password/password';
import {Email} from './email/email';
@Component({
selector: 'user-settings-security',
@@ -29,7 +30,8 @@ import {Password} from './password/password';
TuiInputDirective,
TuiButtonLoading,
TuiErrorComponent,
Password
Password,
Email
],
templateUrl: './security.html',
styleUrl: './security.scss',