From 4453482d93b778dd2ab97ecae43021aff5fa384b Mon Sep 17 00:00:00 2001 From: Joe Milazzo Date: Fri, 11 Apr 2025 09:07:17 -0600 Subject: [PATCH] Polish before Release 2 (#3723) Co-authored-by: Amelia <77553571+Fesaa@users.noreply.github.com> --- API.Tests/API.Tests.csproj | 6 +- API/API.csproj | 22 +++---- API/Controllers/AccountController.cs | 28 +++++--- API/DTOs/RegisterDto.cs | 3 +- .../QueryExtensions/Filtering/SeriesFilter.cs | 4 +- Kavita.Common/Kavita.Common.csproj | 8 +-- UI/Web/package-lock.json | 8 +-- UI/Web/package.json | 2 +- UI/Web/src/app/_services/account.service.ts | 36 +++++----- .../manage-email-settings.component.html | 14 ++-- .../metadata-filter-row.component.html | 2 +- .../metadata-filter-row.component.ts | 14 +++- .../register/register.component.html | 28 ++++---- .../register/register.component.ts | 41 ++++++------ .../setting-item/setting-item.component.ts | 35 ++++++++-- .../side-nav/side-nav.component.ts | 6 ++ .../change-age-restriction.component.html | 2 +- .../change-email/change-email.component.html | 9 +-- .../change-email/change-email.component.ts | 66 ++++++++----------- .../change-password.component.html | 2 +- .../change-password.component.ts | 19 +++--- 21 files changed, 201 insertions(+), 154 deletions(-) diff --git a/API.Tests/API.Tests.csproj b/API.Tests/API.Tests.csproj index 50d08c811..3a4867ec4 100644 --- a/API.Tests/API.Tests.csproj +++ b/API.Tests/API.Tests.csproj @@ -6,11 +6,11 @@ - + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/API/API.csproj b/API/API.csproj index 512174a56..52c390f4e 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -52,7 +52,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -70,11 +70,11 @@ - - - - - + + + + + @@ -92,15 +92,15 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - + + + diff --git a/API/Controllers/AccountController.cs b/API/Controllers/AccountController.cs index 4b280f2cb..c504e1ce7 100644 --- a/API/Controllers/AccountController.cs +++ b/API/Controllers/AccountController.cs @@ -138,6 +138,12 @@ public class AccountController : BaseApiController return BadRequest(usernameValidation); } + // If Email is empty, default to the username + if (string.IsNullOrEmpty(registerDto.Email)) + { + registerDto.Email = registerDto.Username; + } + var user = new AppUserBuilder(registerDto.Username, registerDto.Email, await _unitOfWork.SiteThemeRepository.GetDefaultTheme()).Build(); @@ -352,10 +358,11 @@ public class AccountController : BaseApiController /// /// Returns just if the email was sent or server isn't reachable [HttpPost("update/email")] - public async Task UpdateEmail(UpdateEmailDto? dto) + public async Task> UpdateEmail(UpdateEmailDto? dto) { var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername()); - if (user == null || User.IsInRole(PolicyConstants.ReadOnlyRole)) return Unauthorized(await _localizationService.Translate(User.GetUserId(), "permission-denied")); + if (user == null || User.IsInRole(PolicyConstants.ReadOnlyRole)) + return Unauthorized(await _localizationService.Translate(User.GetUserId(), "permission-denied")); if (dto == null || string.IsNullOrEmpty(dto.Email) || string.IsNullOrEmpty(dto.Password)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "invalid-payload")); @@ -364,12 +371,13 @@ public class AccountController : BaseApiController // Validate this user's password if (! await _userManager.CheckPasswordAsync(user, dto.Password)) { - _logger.LogCritical("A user tried to change {UserName}'s email, but password didn't validate", user.UserName); + _logger.LogWarning("A user tried to change {UserName}'s email, but password didn't validate", user.UserName); return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied")); } // Validate no other users exist with this email - if (user.Email!.Equals(dto.Email)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "nothing-to-do")); + if (user.Email!.Equals(dto.Email)) + return BadRequest(await _localizationService.Translate(User.GetUserId(), "nothing-to-do")); // Check if email is used by another user var existingUserEmail = await _unitOfWork.UserRepository.GetUserByEmailAsync(dto.Email); @@ -386,8 +394,10 @@ public class AccountController : BaseApiController return BadRequest(await _localizationService.Translate(User.GetUserId(), "generate-token")); } + var isValidEmailAddress = _emailService.IsValidEmail(user.Email); var serverSettings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync(); - var shouldEmailUser = serverSettings.IsEmailSetup() || !_emailService.IsValidEmail(user.Email); + var shouldEmailUser = serverSettings.IsEmailSetup() || !isValidEmailAddress; + user.EmailConfirmed = !shouldEmailUser; user.ConfirmationToken = token; await _userManager.UpdateAsync(user); @@ -401,7 +411,8 @@ public class AccountController : BaseApiController return Ok(new InviteUserResponse { EmailLink = string.Empty, - EmailSent = false + EmailSent = false, + InvalidEmail = !isValidEmailAddress }); } @@ -409,7 +420,7 @@ public class AccountController : BaseApiController // Send a confirmation email try { - if (!_emailService.IsValidEmail(user.Email)) + if (!isValidEmailAddress) { _logger.LogCritical("[Update Email]: User is trying to update their email, but their existing email ({Email}) isn't valid. No email will be send", user.Email); return Ok(new InviteUserResponse @@ -441,7 +452,8 @@ public class AccountController : BaseApiController return Ok(new InviteUserResponse { EmailLink = string.Empty, - EmailSent = true + EmailSent = true, + InvalidEmail = !isValidEmailAddress }); } catch (Exception ex) diff --git a/API/DTOs/RegisterDto.cs b/API/DTOs/RegisterDto.cs index d0118e385..2d4d3b77f 100644 --- a/API/DTOs/RegisterDto.cs +++ b/API/DTOs/RegisterDto.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; namespace API.DTOs; +#nullable enable public class RegisterDto { @@ -9,7 +10,7 @@ public class RegisterDto /// /// An email to register with. Optional. Provides Forgot Password functionality /// - public string Email { get; init; } = default!; + public string? Email { get; set; } = default!; [Required] [StringLength(256, MinimumLength = 6)] public string Password { get; set; } = default!; diff --git a/API/Extensions/QueryExtensions/Filtering/SeriesFilter.cs b/API/Extensions/QueryExtensions/Filtering/SeriesFilter.cs index 98878ca9f..ad51a4a62 100644 --- a/API/Extensions/QueryExtensions/Filtering/SeriesFilter.cs +++ b/API/Extensions/QueryExtensions/Filtering/SeriesFilter.cs @@ -510,7 +510,7 @@ public static class SeriesFilter return queries.Aggregate((q1, q2) => q1.Intersect(q2)); case FilterComparison.IsEmpty: - return queryable.Where(s => s.Metadata.Tags == null || s.Metadata.Tags.Count == 0); + return queryable.Where(s => s.Metadata.Tags.Count == 0); case FilterComparison.GreaterThan: case FilterComparison.GreaterThanEqual: case FilterComparison.LessThan: @@ -707,7 +707,7 @@ public static class SeriesFilter return queries.Aggregate((q1, q2) => q1.Intersect(q2)); case FilterComparison.IsEmpty: - return queryable.Where(s => collectionSeries.All(c => c != s.Id)); + return queryable.Where(s => s.Collections.Count == 0); case FilterComparison.GreaterThan: case FilterComparison.GreaterThanEqual: case FilterComparison.LessThan: diff --git a/Kavita.Common/Kavita.Common.csproj b/Kavita.Common/Kavita.Common.csproj index f4d5bc276..66b533102 100644 --- a/Kavita.Common/Kavita.Common.csproj +++ b/Kavita.Common/Kavita.Common.csproj @@ -9,12 +9,12 @@ - + - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/UI/Web/package-lock.json b/UI/Web/package-lock.json index 42c749900..8d6b0ebd6 100644 --- a/UI/Web/package-lock.json +++ b/UI/Web/package-lock.json @@ -39,7 +39,7 @@ "luxon": "^3.6.1", "ng-circle-progress": "^1.7.1", "ng-lazyload-image": "^9.1.3", - "ng-select2-component": "^17.2.3", + "ng-select2-component": "^17.2.4", "ngx-color-picker": "^19.0.0", "ngx-extended-pdf-viewer": "^23.0.0-alpha.7", "ngx-file-drop": "^16.0.0", @@ -7412,9 +7412,9 @@ } }, "node_modules/ng-select2-component": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/ng-select2-component/-/ng-select2-component-17.2.3.tgz", - "integrity": "sha512-JNik7OWqya4ERuqlfnYiJHkaqyZtHqUhATIZ9yUxmadWWNIn8I3Lwa7qt0KtPpR01O9HJC0PtHXhvev88Cju2A==", + "version": "17.2.4", + "resolved": "https://registry.npmjs.org/ng-select2-component/-/ng-select2-component-17.2.4.tgz", + "integrity": "sha512-pfRQg1gY1NsQkBNAYYeSYJjejKwz1z+9bKWor8/8toCNbvh9TYMOKpcz3FrNvhR6v/Hto/quddajaxjD81TOgg==", "dependencies": { "ngx-infinite-scroll": ">=18.0.0 || >=19.0.0", "tslib": "^2.3.0" diff --git a/UI/Web/package.json b/UI/Web/package.json index 912434a63..05d539aed 100644 --- a/UI/Web/package.json +++ b/UI/Web/package.json @@ -47,7 +47,7 @@ "luxon": "^3.6.1", "ng-circle-progress": "^1.7.1", "ng-lazyload-image": "^9.1.3", - "ng-select2-component": "^17.2.3", + "ng-select2-component": "^17.2.4", "ngx-color-picker": "^19.0.0", "ngx-extended-pdf-viewer": "^23.0.0-alpha.7", "ngx-file-drop": "^16.0.0", diff --git a/UI/Web/src/app/_services/account.service.ts b/UI/Web/src/app/_services/account.service.ts index 32b127a1f..6b8cdc243 100644 --- a/UI/Web/src/app/_services/account.service.ts +++ b/UI/Web/src/app/_services/account.service.ts @@ -1,22 +1,20 @@ -import { HttpClient } from '@angular/common/http'; -import {DestroyRef, inject, Injectable } from '@angular/core'; -import {catchError, Observable, of, ReplaySubject, shareReplay, throwError} from 'rxjs'; +import {HttpClient} from '@angular/common/http'; +import {DestroyRef, inject, Injectable} from '@angular/core'; +import {Observable, of, ReplaySubject, shareReplay} from 'rxjs'; import {filter, map, switchMap, tap} from 'rxjs/operators'; -import { environment } from 'src/environments/environment'; -import { Preferences } from '../_models/preferences/preferences'; -import { User } from '../_models/user'; -import { Router } from '@angular/router'; -import { EVENTS, MessageHubService } from './message-hub.service'; -import { ThemeService } from './theme.service'; -import { InviteUserResponse } from '../_models/auth/invite-user-response'; -import { UserUpdateEvent } from '../_models/events/user-update-event'; -import { AgeRating } from '../_models/metadata/age-rating'; -import { AgeRestriction } from '../_models/metadata/age-restriction'; -import { TextResonse } from '../_types/text-response'; +import {environment} from 'src/environments/environment'; +import {Preferences} from '../_models/preferences/preferences'; +import {User} from '../_models/user'; +import {Router} from '@angular/router'; +import {EVENTS, MessageHubService} from './message-hub.service'; +import {ThemeService} from './theme.service'; +import {InviteUserResponse} from '../_models/auth/invite-user-response'; +import {UserUpdateEvent} from '../_models/events/user-update-event'; +import {AgeRating} from '../_models/metadata/age-rating'; +import {AgeRestriction} from '../_models/metadata/age-restriction'; +import {TextResonse} from '../_types/text-response'; import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; import {Action} from "./action-factory.service"; -import {CoverImageSize} from "../admin/_models/cover-image-size"; -import {LicenseInfo} from "../_models/kavitaplus/license-info"; import {LicenseService} from "./license.service"; import {LocalizationService} from "./localization.service"; @@ -132,7 +130,7 @@ export class AccountService { } hasChangeAgeRestrictionRole(user: User) { - return user && user.roles.includes(Role.ChangeRestriction); + return user && !user.roles.includes(Role.Admin) && user.roles.includes(Role.ChangeRestriction); } hasDownloadRole(user: User) { @@ -199,9 +197,9 @@ export class AccountService { if (this.currentUser) { // BUG: StopHubConnection has a promise in it, this needs to be async // But that really messes everything up - this.messageHub.stopHubConnection(); - this.messageHub.createHubConnection(this.currentUser); if (!isSameUser) { + this.messageHub.stopHubConnection(); + this.messageHub.createHubConnection(this.currentUser); this.licenseService.hasValidLicense().subscribe(); } this.startRefreshTokenTimer(); diff --git a/UI/Web/src/app/admin/manage-email-settings/manage-email-settings.component.html b/UI/Web/src/app/admin/manage-email-settings/manage-email-settings.component.html index 5fb0ea732..3107fc072 100644 --- a/UI/Web/src/app/admin/manage-email-settings/manage-email-settings.component.html +++ b/UI/Web/src/app/admin/manage-email-settings/manage-email-settings.component.html @@ -17,12 +17,8 @@ {{formControl.value | defaultValue}} -
- - - -
+ @if (formControl.errors; as errors) {
@@ -69,7 +65,11 @@ {{formControl.value | defaultValue}} - +
+ + + +
} diff --git a/UI/Web/src/app/metadata-filter/_components/metadata-filter-row/metadata-filter-row.component.html b/UI/Web/src/app/metadata-filter/_components/metadata-filter-row/metadata-filter-row.component.html index 97a8f6c5a..f82f36a42 100644 --- a/UI/Web/src/app/metadata-filter/_components/metadata-filter-row/metadata-filter-row.component.html +++ b/UI/Web/src/app/metadata-filter/_components/metadata-filter-row/metadata-filter-row.component.html @@ -18,7 +18,7 @@
- @if (formGroup.get('comparison')?.value !== FilterComparison.IsEmpty) { + @if (IsEmptySelected) { @if (predicateType$ | async; as predicateType) { @switch (predicateType) { @case (PredicateType.Text) { diff --git a/UI/Web/src/app/metadata-filter/_components/metadata-filter-row/metadata-filter-row.component.ts b/UI/Web/src/app/metadata-filter/_components/metadata-filter-row/metadata-filter-row.component.ts index 26abbdb95..34a1b7db8 100644 --- a/UI/Web/src/app/metadata-filter/_components/metadata-filter-row/metadata-filter-row.component.ts +++ b/UI/Web/src/app/metadata-filter/_components/metadata-filter-row/metadata-filter-row.component.ts @@ -170,6 +170,10 @@ export class MetadataFilterRowComponent implements OnInit { private readonly mangaFormatPipe = new MangaFormatPipe(this.translocoService); private readonly ageRatingPipe = new AgeRatingPipe(); + get IsEmptySelected() { + return parseInt(this.formGroup.get('comparison')?.value + '', 10) !== FilterComparison.IsEmpty; + } + get UiLabel(): FilterRowUi | null { const field = parseInt(this.formGroup.get('input')!.value, 10) as FilterField; @@ -348,6 +352,7 @@ export class MetadataFilterRowComponent implements OnInit { this.formGroup.get('filterValue')?.patchValue(''); this.formGroup.get('comparison')?.patchValue(StringComparisons[0]); } + this.cdRef.markForCheck(); return; } @@ -363,10 +368,13 @@ export class MetadataFilterRowComponent implements OnInit { this.validComparisons$.next([...new Set(comps)]); this.predicateType$.next(PredicateType.Number); + if (this.loaded) { this.formGroup.get('filterValue')?.patchValue(0); this.formGroup.get('comparison')?.patchValue(NumberComparisons[0]); } + + this.cdRef.markForCheck(); return; } @@ -383,6 +391,7 @@ export class MetadataFilterRowComponent implements OnInit { this.formGroup.get('filterValue')?.patchValue(false); this.formGroup.get('comparison')?.patchValue(DateComparisons[0]); } + this.cdRef.markForCheck(); return; } @@ -400,6 +409,7 @@ export class MetadataFilterRowComponent implements OnInit { this.formGroup.get('filterValue')?.patchValue(false); this.formGroup.get('comparison')?.patchValue(BooleanComparisons[0]); } + this.cdRef.markForCheck(); return; } @@ -421,15 +431,15 @@ export class MetadataFilterRowComponent implements OnInit { this.formGroup.get('filterValue')?.patchValue(0); this.formGroup.get('comparison')?.patchValue(comps[0]); } + this.cdRef.markForCheck(); return; } } - - onDateSelect(_: NgbDate) { this.propagateFilterUpdate(); } + updateIfDateFilled() { this.propagateFilterUpdate(); } diff --git a/UI/Web/src/app/registration/_components/register/register.component.html b/UI/Web/src/app/registration/_components/register/register.component.html index a92182415..0ecd3894e 100644 --- a/UI/Web/src/app/registration/_components/register/register.component.html +++ b/UI/Web/src/app/registration/_components/register/register.component.html @@ -8,11 +8,13 @@ -
-
- {{t('required-field')}} + @if (registerForm.dirty || registerForm.touched) { +
+ @if (registerForm.get('username')?.errors?.required) { +
{{t('required-field')}}
+ }
-
+ }
@@ -22,16 +24,18 @@ - -
-
- {{t('required-field')}} + @if (registerForm.dirty || registerForm.touched) { +
+ @if (registerForm.get('email')?.errors?.required) { +
{{t('required-field')}}
+ } @else if (registerForm.get('email')?.errors?.email) { +
{{t('valid-email')}}
+ }
-
- {{t('valid-email')}} -
-
+ } +
diff --git a/UI/Web/src/app/registration/_components/register/register.component.ts b/UI/Web/src/app/registration/_components/register/register.component.ts index 48de88661..ecad07f27 100644 --- a/UI/Web/src/app/registration/_components/register/register.component.ts +++ b/UI/Web/src/app/registration/_components/register/register.component.ts @@ -1,13 +1,13 @@ import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; -import { FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angular/forms'; -import { Router } from '@angular/router'; -import { ToastrService } from 'ngx-toastr'; -import { take } from 'rxjs/operators'; -import { AccountService } from 'src/app/_services/account.service'; -import { MemberService } from 'src/app/_services/member.service'; -import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; -import { NgIf, NgTemplateOutlet } from '@angular/common'; -import { SplashContainerComponent } from '../splash-container/splash-container.component'; +import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; +import {Router} from '@angular/router'; +import {ToastrService} from 'ngx-toastr'; +import {take} from 'rxjs/operators'; +import {AccountService} from 'src/app/_services/account.service'; +import {MemberService} from 'src/app/_services/member.service'; +import {NgbTooltip} from '@ng-bootstrap/ng-bootstrap'; +import {NgTemplateOutlet} from '@angular/common'; +import {SplashContainerComponent} from '../splash-container/splash-container.component'; import {translate, TranslocoDirective} from "@jsverse/transloco"; import {NavService} from "../../../_services/nav.service"; @@ -15,25 +15,28 @@ import {NavService} from "../../../_services/nav.service"; * This is exclusively used to register the first user on the server and nothing else */ @Component({ - selector: 'app-register', - templateUrl: './register.component.html', - styleUrls: ['./register.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [SplashContainerComponent, ReactiveFormsModule, NgIf, NgbTooltip, NgTemplateOutlet, TranslocoDirective] + selector: 'app-register', + templateUrl: './register.component.html', + styleUrls: ['./register.component.scss'], + imports: [SplashContainerComponent, ReactiveFormsModule, NgbTooltip, NgTemplateOutlet, TranslocoDirective], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class RegisterComponent { + private readonly navService = inject(NavService); + private readonly router = inject(Router); + private readonly accountService = inject(AccountService); + private readonly toastr = inject(ToastrService); + private readonly memberService = inject(MemberService); + registerForm: FormGroup = new FormGroup({ - email: new FormControl('', [Validators.required]), username: new FormControl('', [Validators.required]), + email: new FormControl('', []), password: new FormControl('', [Validators.required, Validators.maxLength(256), Validators.minLength(6), Validators.pattern("^.{6,256}$")]), }); - private readonly navService = inject(NavService); - - constructor(private router: Router, private accountService: AccountService, - private toastr: ToastrService, private memberService: MemberService) { + constructor() { this.navService.hideNavBar(); this.navService.hideSideNav(); diff --git a/UI/Web/src/app/settings/_components/setting-item/setting-item.component.ts b/UI/Web/src/app/settings/_components/setting-item/setting-item.component.ts index cb4295c28..a710b34c4 100644 --- a/UI/Web/src/app/settings/_components/setting-item/setting-item.component.ts +++ b/UI/Web/src/app/settings/_components/setting-item/setting-item.component.ts @@ -2,9 +2,16 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - ContentChild, ElementRef, EventEmitter, HostListener, + ContentChild, + ElementRef, + EventEmitter, + HostListener, inject, - Input, OnChanges, Output, SimpleChange, SimpleChanges, + Input, + OnChanges, + Output, + SimpleChange, + SimpleChanges, TemplateRef } from '@angular/core'; import {TranslocoDirective} from "@jsverse/transloco"; @@ -28,6 +35,7 @@ import {AbstractControl} from "@angular/forms"; export class SettingItemComponent implements OnChanges { private readonly cdRef = inject(ChangeDetectorRef); + private readonly elementRef = inject(ElementRef); @Input({required:true}) title: string = ''; @Input() editLabel: string | undefined = undefined; @@ -98,11 +106,10 @@ export class SettingItemComponent implements OnChanges { if (!this.canEdit) return; if (this.control != null && this.control.invalid) return; - console.log('isEditMode', this.isEditMode, 'currentValue', change.currentValue); this.isEditMode = change.currentValue; - //this.editMode.emit(this.isEditMode); this.cdRef.markForCheck(); + this.focusInput(); } } @@ -114,7 +121,27 @@ export class SettingItemComponent implements OnChanges { this.isEditMode = !this.isEditMode; this.editMode.emit(this.isEditMode); + this.focusInput(); this.cdRef.markForCheck(); } + focusInput() { + if (this.isEditMode) { + + + setTimeout(() => { + const inputElem = this.findFirstInput(); + if (inputElem) { + inputElem.focus(); + } + }, 10); + } + } + + private findFirstInput(): HTMLInputElement | null { + const nativeInputs = [...this.elementRef.nativeElement.querySelectorAll('input'), ...this.elementRef.nativeElement.querySelectorAll('select'), ...this.elementRef.nativeElement.querySelectorAll('textarea')]; + if (nativeInputs.length === 0) return null; + + return nativeInputs[0]; + } } diff --git a/UI/Web/src/app/sidenav/_components/side-nav/side-nav.component.ts b/UI/Web/src/app/sidenav/_components/side-nav/side-nav.component.ts index 351042725..9ac7df15c 100644 --- a/UI/Web/src/app/sidenav/_components/side-nav/side-nav.component.ts +++ b/UI/Web/src/app/sidenav/_components/side-nav/side-nav.component.ts @@ -239,6 +239,12 @@ export class SideNavComponent implements OnInit { } async reorderDrop($event: CdkDragDrop) { + // Don't allow dropping on non SideNav items + const fixedSideNavItems = 3; + if ($event.currentIndex < fixedSideNavItems) { + return; + } + const stream = $event.item.data; // Offset the home, back, and customize button this.navService.updateSideNavStreamPosition(stream.name, stream.id, stream.order, $event.currentIndex - 3).subscribe({ diff --git a/UI/Web/src/app/user-settings/change-age-restriction/change-age-restriction.component.html b/UI/Web/src/app/user-settings/change-age-restriction/change-age-restriction.component.html index 59a8556fa..f24fb028c 100644 --- a/UI/Web/src/app/user-settings/change-age-restriction/change-age-restriction.component.html +++ b/UI/Web/src/app/user-settings/change-age-restriction/change-age-restriction.component.html @@ -1,6 +1,6 @@ @if (user) { - + {{user.ageRestriction.ageRating | ageRating }} @if (user.ageRestriction.ageRating !== AgeRating.NotApplicable && user.ageRestriction.includeUnknowns) { diff --git a/UI/Web/src/app/user-settings/change-email/change-email.component.html b/UI/Web/src/app/user-settings/change-email/change-email.component.html index fd1779b2a..d58400e88 100644 --- a/UI/Web/src/app/user-settings/change-email/change-email.component.html +++ b/UI/Web/src/app/user-settings/change-email/change-email.component.html @@ -1,7 +1,8 @@ - - + + + {{user?.email}} @if(emailConfirmed) { {{t('email-confirmed')}} @@ -11,10 +12,6 @@ } - - {{user?.email}} - - @if (errors.length > 0) {