Kavita/UI/Web/src/app/admin/manage-users/manage-users.component.ts
2025-03-23 13:10:50 -07:00

160 lines
6.1 KiB
TypeScript

import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit} from '@angular/core';
import {NgbModal, NgbTooltip} from '@ng-bootstrap/ng-bootstrap';
import {take} from 'rxjs/operators';
import {MemberService} from 'src/app/_services/member.service';
import {Member} from 'src/app/_models/auth/member';
import {AccountService} from 'src/app/_services/account.service';
import {ToastrService} from 'ngx-toastr';
import {ResetPasswordModalComponent} from '../_modals/reset-password-modal/reset-password-modal.component';
import {ConfirmService} from 'src/app/shared/confirm.service';
import {MessageHubService} from 'src/app/_services/message-hub.service';
import {InviteUserComponent} from '../invite-user/invite-user.component';
import {EditUserComponent} from '../edit-user/edit-user.component';
import {Router} from '@angular/router';
import {TagBadgeComponent} from '../../shared/tag-badge/tag-badge.component';
import {AsyncPipe, DatePipe, NgClass, NgIf, TitleCasePipe} from '@angular/common';
import {translate, TranslocoModule, TranslocoService} from "@jsverse/transloco";
import {DefaultDatePipe} from "../../_pipes/default-date.pipe";
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
import {ReadMoreComponent} from "../../shared/read-more/read-more.component";
import {UtcToLocalTimePipe} from "../../_pipes/utc-to-local-time.pipe";
import {makeBindingParser} from "@angular/compiler";
import {LoadingComponent} from "../../shared/loading/loading.component";
import {TimeAgoPipe} from "../../_pipes/time-ago.pipe";
import {SentenceCasePipe} from "../../_pipes/sentence-case.pipe";
import {DefaultModalOptions} from "../../_models/default-modal-options";
import {UtcToLocaleDatePipe} from "../../_pipes/utc-to-locale-date.pipe";
@Component({
selector: 'app-manage-users',
templateUrl: './manage-users.component.html',
styleUrls: ['./manage-users.component.scss'],
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NgbTooltip, TagBadgeComponent, AsyncPipe, TitleCasePipe, DatePipe, TranslocoModule, DefaultDatePipe, NgClass, DefaultValuePipe, ReadMoreComponent, UtcToLocalTimePipe, LoadingComponent, NgIf, TimeAgoPipe, SentenceCasePipe, UtcToLocaleDatePipe]
})
export class ManageUsersComponent implements OnInit {
members: Member[] = [];
loggedInUsername = '';
loadingMembers = false;
private readonly translocoService = inject(TranslocoService);
private readonly cdRef = inject(ChangeDetectorRef);
private readonly memberService = inject(MemberService);
private readonly accountService = inject(AccountService);
private readonly modalService = inject(NgbModal);
private readonly toastr = inject(ToastrService);
private readonly confirmService = inject(ConfirmService);
public readonly messageHub = inject(MessageHubService);
private readonly router = inject(Router);
constructor() {
this.accountService.currentUser$.pipe(take(1)).subscribe((user) => {
if (user) {
this.loggedInUsername = user.username;
this.cdRef.markForCheck();
}
});
}
ngOnInit(): void {
this.loadMembers();
}
loadMembers() {
this.loadingMembers = true;
this.cdRef.markForCheck();
this.memberService.getMembers(true).subscribe(members => {
this.members = members;
// Show logged-in user at the top of the list
this.members.sort((a: Member, b: Member) => {
if (a.username === this.loggedInUsername) return 1;
if (b.username === this.loggedInUsername) return 1;
const nameA = a.username.toUpperCase();
const nameB = b.username.toUpperCase();
if (nameA < nameB) return -1;
if (nameA > nameB) return 1;
return 0;
})
this.loadingMembers = false;
this.cdRef.markForCheck();
});
}
canEditMember(member: Member): boolean {
return this.loggedInUsername !== member.username;
}
openEditUser(member: Member) {
const modalRef = this.modalService.open(EditUserComponent, DefaultModalOptions);
modalRef.componentInstance.member = member;
modalRef.closed.subscribe(() => {
this.loadMembers();
});
}
async deleteUser(member: Member) {
if (await this.confirmService.confirm(this.translocoService.translate('toasts.confirm-delete-user'))) {
this.memberService.deleteMember(member.username).subscribe(() => {
setTimeout(() => {
this.loadMembers();
this.toastr.success(this.translocoService.translate('toasts.user-deleted', {user: member.username}));
}, 30); // SetTimeout because I've noticed this can run super fast and not give enough time for data to flush
});
}
}
inviteUser() {
const modalRef = this.modalService.open(InviteUserComponent, DefaultModalOptions);
modalRef.closed.subscribe((successful: boolean) => {
this.loadMembers();
});
}
resendEmail(member: Member) {
this.accountService.resendConfirmationEmail(member.id).subscribe(async (response) => {
if (response.emailSent) {
this.toastr.info(this.translocoService.translate('toasts.email-sent', {email: member.username}));
return;
}
await this.confirmService.alert(
this.translocoService.translate('toasts.click-email-link') + '<br/> <a href="' + response.emailLink + '" target="_blank" rel="noopener noreferrer">' + response.emailLink + '</a>');
});
}
setup(member: Member) {
this.accountService.getInviteUrl(member.id, false).subscribe(url => {
if (url) {
this.router.navigateByUrl(url);
}
});
}
updatePassword(member: Member) {
const modalRef = this.modalService.open(ResetPasswordModalComponent, DefaultModalOptions);
modalRef.componentInstance.member = member;
}
formatLibraries(member: Member) {
if (member.libraries.length === 0) {
return translate('manage-users.none');
}
return member.libraries.map(item => item.name).join(', ');
}
hasAdminRole(member: Member) {
return member.roles.indexOf('Admin') >= 0;
}
getRoles(member: Member) {
return member.roles.filter(item => item != 'Pleb');
}
protected readonly makeBindingParser = makeBindingParser;
}