Random Bugs (#2531)
This commit is contained in:
parent
0c70e80420
commit
4e1c66331f
27 changed files with 232 additions and 178 deletions
|
@ -7,4 +7,8 @@ export interface InviteUserResponse {
|
|||
* If an email was sent to the invited user
|
||||
*/
|
||||
emailSent: boolean;
|
||||
}
|
||||
/**
|
||||
* When a user has an invalid email and is attempting to perform a flow.
|
||||
*/
|
||||
invalidEmail: boolean;
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
export interface UpdateEmailResponse {
|
||||
/**
|
||||
* Did the user not have an existing email
|
||||
*/
|
||||
hadNoExistingEmail: boolean;
|
||||
/**
|
||||
* Was an email sent (ie is this server accessible)
|
||||
*/
|
||||
emailSent: boolean;
|
||||
}
|
|
@ -10,12 +10,10 @@ 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 { UpdateEmailResponse } from '../_models/auth/update-email-response';
|
||||
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 {ToastrService} from "ngx-toastr";
|
||||
|
||||
export enum Role {
|
||||
Admin = 'Admin',
|
||||
|
@ -31,7 +29,6 @@ export enum Role {
|
|||
export class AccountService {
|
||||
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly toastr = inject(ToastrService);
|
||||
|
||||
baseUrl = environment.apiUrl;
|
||||
userKey = 'kavita-user';
|
||||
|
@ -192,8 +189,9 @@ export class AccountService {
|
|||
return this.httpClient.get<boolean>(this.baseUrl + 'account/email-confirmed');
|
||||
}
|
||||
|
||||
migrateUser(model: {email: string, username: string, password: string, sendEmail: boolean}) {
|
||||
return this.httpClient.post<string>(this.baseUrl + 'account/migrate-email', model, TextResonse);
|
||||
isEmailValid() {
|
||||
return this.httpClient.get<string>(this.baseUrl + 'account/is-email-valid', TextResonse)
|
||||
.pipe(map(res => res == "true"));
|
||||
}
|
||||
|
||||
confirmMigrationEmail(model: {email: string, token: string}) {
|
||||
|
@ -247,7 +245,7 @@ export class AccountService {
|
|||
}
|
||||
|
||||
updateEmail(email: string, password: string) {
|
||||
return this.httpClient.post<UpdateEmailResponse>(this.baseUrl + 'account/update/email', {email, password});
|
||||
return this.httpClient.post<InviteUserResponse>(this.baseUrl + 'account/update/email', {email, password});
|
||||
}
|
||||
|
||||
updateAgeRestriction(ageRating: AgeRating, includeUnknowns: boolean) {
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
<div *ngIf="userForm.get('username')?.errors?.required">
|
||||
{{t('required')}}
|
||||
</div>
|
||||
<div *ngIf="userForm.get('username')?.errors?.pattern">
|
||||
{{t('username-pattern', {characters: allowedCharacters})}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -12,6 +12,8 @@ import { RoleSelectorComponent } from '../role-selector/role-selector.component'
|
|||
import { NgIf } from '@angular/common';
|
||||
import {TranslocoDirective} from "@ngneat/transloco";
|
||||
|
||||
const AllowedUsernameCharacters = /^[\sa-zA-Z0-9\-._@+/\s]*$/;
|
||||
|
||||
@Component({
|
||||
selector: 'app-edit-user',
|
||||
templateUrl: './edit-user.component.html',
|
||||
|
@ -30,6 +32,8 @@ export class EditUserComponent implements OnInit {
|
|||
|
||||
userForm: FormGroup = new FormGroup({});
|
||||
|
||||
allowedCharacters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+/';
|
||||
|
||||
public get email() { return this.userForm.get('email'); }
|
||||
public get username() { return this.userForm.get('username'); }
|
||||
public get password() { return this.userForm.get('password'); }
|
||||
|
@ -39,7 +43,7 @@ export class EditUserComponent implements OnInit {
|
|||
|
||||
ngOnInit(): void {
|
||||
this.userForm.addControl('email', new FormControl(this.member.email, [Validators.required, Validators.email]));
|
||||
this.userForm.addControl('username', new FormControl(this.member.username, [Validators.required]));
|
||||
this.userForm.addControl('username', new FormControl(this.member.username, [Validators.required, Validators.pattern(AllowedUsernameCharacters)]));
|
||||
|
||||
this.userForm.get('email')?.disable();
|
||||
this.selectedRestriction = this.member.ageRestriction;
|
||||
|
|
|
@ -39,8 +39,12 @@
|
|||
|
||||
<ng-container *ngIf="emailLink !== ''">
|
||||
<h4>{{t('setup-user-title')}}</h4>
|
||||
<p>{{t('setup-user-description')}}
|
||||
</p>
|
||||
<p>{{t('setup-user-description')}}</p>
|
||||
@if (inviteError) {
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<strong>{{t('notice')}}</strong> {{t('email-not-sent')}}
|
||||
</div>
|
||||
}
|
||||
<a class="email-link" href="{{emailLink}}" target="_blank" rel="noopener noreferrer">{{t('setup-user-account')}}</a>
|
||||
<app-api-key [title]="t('invite-url-label')" [tooltipText]="t('setup-user-account-tooltip')" [hideData]="false" [showRefresh]="false" [transform]="makeLink"></app-api-key>
|
||||
</ng-container>
|
||||
|
|
|
@ -34,6 +34,7 @@ export class InviteUserComponent implements OnInit {
|
|||
selectedRestriction: AgeRestriction = {ageRating: AgeRating.NotApplicable, includeUnknowns: false};
|
||||
emailLink: string = '';
|
||||
invited: boolean = false;
|
||||
inviteError: boolean = false;
|
||||
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
|
||||
|
@ -65,14 +66,24 @@ export class InviteUserComponent implements OnInit {
|
|||
this.emailLink = data.emailLink;
|
||||
this.isSending = false;
|
||||
this.invited = true;
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
if (data.invalidEmail) {
|
||||
this.toastr.info(translate('toasts.email-not-sent'));
|
||||
this.inviteError = true;
|
||||
this.cdRef.markForCheck();
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.emailSent) {
|
||||
this.toastr.info(translate('toasts.email-sent', {email: email}));
|
||||
this.modal.close(true);
|
||||
}
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
}, err => {
|
||||
// Note to self: If you need to catch an error, do it, but don't toast because interceptor handles that
|
||||
this.isSending = false;
|
||||
this.toastr.error(err)
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -115,7 +115,7 @@ export class ManageUsersComponent implements OnInit {
|
|||
this.serverService.isServerAccessible().subscribe(canAccess => {
|
||||
this.accountService.resendConfirmationEmail(member.id).subscribe(async (email) => {
|
||||
if (canAccess) {
|
||||
this.toastr.info(this.translocoService.translate('toasts.email-sent-to-user', {user: member.username}));
|
||||
this.toastr.info(this.translocoService.translate('toasts.email-sent', {user: member.username}));
|
||||
return;
|
||||
}
|
||||
await this.confirmService.alert(
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import {CommonModule, DOCUMENT} from '@angular/common';
|
||||
import {
|
||||
afterNextRender,
|
||||
AfterRenderPhase, AfterViewInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
|
@ -172,6 +170,7 @@ export class CardDetailLayoutComponent implements OnInit, OnChanges {
|
|||
}
|
||||
|
||||
hasCustomSort() {
|
||||
if (this.filteringDisabled) return false;
|
||||
return this.filter?.sortOptions?.sortField != SortField.SortName || !this.filter?.sortOptions.isAscending
|
||||
|| this.filterSettings?.presetsV2?.sortOptions?.sortField != SortField.SortName || !this.filterSettings?.presetsV2?.sortOptions?.isAscending;
|
||||
}
|
||||
|
|
|
@ -318,8 +318,6 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
const companionHeight = this.companionBar!.nativeElement.offsetHeight;
|
||||
const navbarHeight = navbar.offsetHeight;
|
||||
const totalHeight = companionHeight + navbarHeight + 21; //21px to account for padding
|
||||
console.log('compainionHeight: ', companionHeight)
|
||||
console.log('navbarHeight: ', navbarHeight)
|
||||
return 'calc(var(--vh)*100 - ' + totalHeight + 'px)';
|
||||
}
|
||||
|
||||
|
|
|
@ -73,6 +73,6 @@
|
|||
<div class="side-nav-overlay" (click)="toggleNavBar()" [ngClass]="{'closed' : (navService.sideNavCollapsed$ | async)}"></div>
|
||||
<div class="bottom" [ngClass]="{'closed' : (navService.sideNavCollapsed$ | async),
|
||||
'hidden': (navService.sideNavVisibility$ | async) === false || (accountService.hasValidLicense$ | async) === true}">
|
||||
<app-side-nav-item *ngIf="(accountService.hasValidLicense$ | async) === false" [ngClass]="'donate'" icon="fa-heart" [title]="t('donate')" link="https://opencollective.com/kavita" [external]="true"></app-side-nav-item>
|
||||
<app-side-nav-item *ngIf="(accountService.hasValidLicense$ | async) === false" [ngClass]="'donate'" icon="fa-heart" [ngbTooltip]="t('donate-tooltip')" link="https://wiki.kavitareader.com/en/faq#q-i-want-to-donate-what-are-my-options" [external]="true"></app-side-nav-item>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
OnInit
|
||||
} from '@angular/core';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import {NgbModal, NgbTooltip} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {distinctUntilChanged, filter, map, take, tap} from 'rxjs/operators';
|
||||
import { ImportCblModalComponent } from 'src/app/reading-list/_modals/import-cbl-modal/import-cbl-modal.component';
|
||||
import { ImageService } from 'src/app/_services/image.service';
|
||||
|
@ -34,7 +34,7 @@ import {SideNavStreamType} from "../../../_models/sidenav/sidenav-stream-type.en
|
|||
@Component({
|
||||
selector: 'app-side-nav',
|
||||
standalone: true,
|
||||
imports: [CommonModule, SideNavItemComponent, CardActionablesComponent, FilterPipe, FormsModule, TranslocoDirective, SentenceCasePipe],
|
||||
imports: [CommonModule, SideNavItemComponent, CardActionablesComponent, FilterPipe, FormsModule, TranslocoDirective, SentenceCasePipe, NgbTooltip],
|
||||
templateUrl: './side-nav.component.html',
|
||||
styleUrls: ['./side-nav.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
|
|
|
@ -29,14 +29,22 @@
|
|||
<div *ngFor="let error of errors">{{error}}</div>
|
||||
</div>
|
||||
<form [formGroup]="form">
|
||||
@if(!hasValidEmail) {
|
||||
<div class="alert alert-warning" role="alert">
|
||||
{{t('has-invalid-email')}}
|
||||
</div>
|
||||
}
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label visually-hidden">{{t('email-label')}}</label>
|
||||
<input class="form-control custom-input" type="email" id="email" formControlName="email"
|
||||
[class.is-invalid]="form.get('email')?.invalid && form.get('email')?.touched">
|
||||
<div id="email-validations" class="invalid-feedback" *ngIf="form.dirty || form.touched">
|
||||
<div id="email-validations" class="invalid-feedback" *ngIf="form.get('email')?.errors">
|
||||
<div *ngIf="form.get('email')?.errors?.required">
|
||||
{{t('required-field')}}
|
||||
</div>
|
||||
<div *ngIf="form.get('email')?.errors?.email">
|
||||
{{t('valid-email')}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -2,13 +2,12 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, injec
|
|||
import { FormControl, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import {ToastrService} from 'ngx-toastr';
|
||||
import {shareReplay, take} from 'rxjs';
|
||||
import {UpdateEmailResponse} from 'src/app/_models/auth/update-email-response';
|
||||
import {User} from 'src/app/_models/user';
|
||||
import {AccountService} from 'src/app/_services/account.service';
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import { ApiKeyComponent } from '../api-key/api-key.component';
|
||||
import { NgbTooltip, NgbCollapse } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { NgIf, NgFor } from '@angular/common';
|
||||
import {NgIf, NgFor, JsonPipe} from '@angular/common';
|
||||
import {translate, TranslocoDirective} from "@ngneat/transloco";
|
||||
|
||||
@Component({
|
||||
|
@ -17,17 +16,20 @@ import {translate, TranslocoDirective} from "@ngneat/transloco";
|
|||
styleUrls: ['./change-email.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [NgIf, NgbTooltip, NgbCollapse, NgFor, ReactiveFormsModule, ApiKeyComponent, TranslocoDirective]
|
||||
imports: [NgIf, NgbTooltip, NgbCollapse, NgFor, ReactiveFormsModule, ApiKeyComponent, TranslocoDirective, JsonPipe]
|
||||
})
|
||||
export class ChangeEmailComponent implements OnInit {
|
||||
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
|
||||
form: FormGroup = new FormGroup({});
|
||||
user: User | undefined = undefined;
|
||||
errors: string[] = [];
|
||||
isViewMode: boolean = true;
|
||||
emailLink: string = '';
|
||||
emailConfirmed: boolean = true;
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
hasValidEmail: boolean = true;
|
||||
|
||||
|
||||
public get email() { return this.form.get('email'); }
|
||||
|
||||
|
@ -45,6 +47,10 @@ export class ChangeEmailComponent implements OnInit {
|
|||
this.emailConfirmed = confirmed;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
this.accountService.isEmailValid().subscribe(isValid => {
|
||||
this.hasValidEmail = isValid;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -59,13 +65,14 @@ export class ChangeEmailComponent implements OnInit {
|
|||
|
||||
const model = this.form.value;
|
||||
this.errors = [];
|
||||
this.accountService.updateEmail(model.email, model.password).subscribe((updateEmailResponse: UpdateEmailResponse) => {
|
||||
this.accountService.updateEmail(model.email, model.password).subscribe(updateEmailResponse => {
|
||||
|
||||
if (updateEmailResponse.invalidEmail) {
|
||||
this.toastr.success(translate('toasts.email-sent-to-no-existing', {email: model.email}));
|
||||
}
|
||||
|
||||
if (updateEmailResponse.emailSent) {
|
||||
if (updateEmailResponse.hadNoExistingEmail) {
|
||||
this.toastr.success(translate('toasts.email-sent-to-no-existing', {email: model.email}));
|
||||
} else {
|
||||
this.toastr.success(translate('toasts.email-sent-to'));
|
||||
}
|
||||
this.toastr.success(translate('toasts.email-sent-to'));
|
||||
} else {
|
||||
this.toastr.success(translate('toasts.change-email-private'));
|
||||
}
|
||||
|
|
|
@ -250,9 +250,11 @@
|
|||
"setup-user-account": "Setup user's account",
|
||||
"invite-url-label": "Invite Url",
|
||||
"invite-url-tooltip": "Copy this and paste in a new tab",
|
||||
"has-invalid-email": "It looks like you do not have a valid email set. Change email will require the admin to send you a link to complete this action.",
|
||||
|
||||
"permission-error": "You do not have permission to change your email. Reach out to the admin of the server.",
|
||||
"required-field": "{{validation.required-field}}",
|
||||
"valid-email": "{{validation.valid-email}}",
|
||||
"reset": "{{common.reset}}",
|
||||
"edit": "{{common.edit}}",
|
||||
"cancel": "{{common.cancel}}",
|
||||
|
@ -562,7 +564,9 @@
|
|||
"invite-url-label": "Invite Url",
|
||||
"invite": "Invite",
|
||||
"inviting": "Inviting…",
|
||||
"cancel": "{{common.cancel}}"
|
||||
"cancel": "{{common.cancel}}",
|
||||
"email-not-sent": "{{toasts.email-not-sent}}",
|
||||
"notice": "{{manage-settings.notice}}"
|
||||
},
|
||||
|
||||
"library-selector": {
|
||||
|
@ -772,6 +776,7 @@
|
|||
"all-series": "All Series",
|
||||
"clear": "{{common.clear}}",
|
||||
"donate": "Donate",
|
||||
"donate-tooltip": "You can remove this by subscribing to Kavita+",
|
||||
"back": "Back",
|
||||
"more": "More"
|
||||
},
|
||||
|
@ -1252,6 +1257,7 @@
|
|||
"delete-user-alt": "Delete User {{user}}",
|
||||
"edit-user-tooltip": "Edit",
|
||||
"edit-user-alt": "Edit User {{user}}",
|
||||
"username-pattern": "Username can only contain the following characters and whitespace: {{characters}}",
|
||||
"resend-invite-tooltip": "Resend Invite",
|
||||
"resend-invite-alt": "Resend Invite {{user}}",
|
||||
"setup-user-tooltip": "Setup User",
|
||||
|
@ -1924,7 +1930,6 @@
|
|||
"no-updates": "No updates available",
|
||||
"confirm-delete-user": "Are you sure you want to delete this user?",
|
||||
"user-deleted": "{{user}} has been deleted",
|
||||
"email-sent-to-user": "Email sent to {{user}}",
|
||||
"click-email-link": "Please click this link to confirm your email. You must confirm to be able to login.",
|
||||
"series-added-to-collection": "Series added to {{collectionName}} collection",
|
||||
"no-series-collection-warning": "Warning! No series are selected, saving will delete the Collection. Are you sure you want to continue?",
|
||||
|
@ -1955,6 +1960,7 @@
|
|||
"file-send-to": "File(s) emailed to {{name}}",
|
||||
"theme-missing": "The active theme no longer exists. Please refresh the page.",
|
||||
"email-sent": "Email sent to {{email}}",
|
||||
"email-not-sent": "Email on file is not a valid email and can not be sent. A link has been dumped in logs. The admin can provide this link to complete flow.",
|
||||
"k+-license-saved": "License Key saved, but it is not valid. Click check to revalidate the subscription. First time registration may take a min to propagate.",
|
||||
"k+-unlocked": "Kavita+ unlocked!",
|
||||
"k+-error": "There was an error when activating your license. Please try again.",
|
||||
|
@ -1976,7 +1982,7 @@
|
|||
"library-created": "Library created successfully. A scan has been started.",
|
||||
"anilist-token-updated": "AniList Token has been updated",
|
||||
"age-restriction-updated": "Age Restriction has been updated",
|
||||
"email-sent-to-no-existing": "An email has been sent to {{email}} for confirmation.",
|
||||
"email-sent-to-no-existing": "Existing email is not valid. A link has been dumped to logs. Ask admin for link to complete email change.",
|
||||
"email-sent-to": "An email has been sent to your old email address for confirmation.",
|
||||
"change-email-private": "The server is not publicly accessible. Ask the admin to fetch your confirmation link from the logs",
|
||||
"device-updated": "Device updated",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue