107 lines
3.4 KiB
TypeScript
107 lines
3.4 KiB
TypeScript
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
import { DialogsModule } from '@progress/kendo-angular-dialog';
|
|
import { InputsModule } from '@progress/kendo-angular-inputs';
|
|
import { LabelModule } from '@progress/kendo-angular-label';
|
|
import { ButtonsModule } from '@progress/kendo-angular-buttons';
|
|
import { IndicatorsModule } from '@progress/kendo-angular-indicators';
|
|
import { MemberListItemDto, memberDisplayName } from '../../models/member.model';
|
|
import { InvitationApiService } from '../../services/invitation-api.service';
|
|
import { ToastService } from '../../../../core/services/toast.service';
|
|
|
|
type Step = 'needEmail' | 'generating' | 'ready';
|
|
|
|
@Component({
|
|
selector: 'app-invitation-dialog',
|
|
standalone: true,
|
|
imports: [
|
|
CommonModule, ReactiveFormsModule, DialogsModule, InputsModule,
|
|
LabelModule, ButtonsModule, IndicatorsModule,
|
|
],
|
|
templateUrl: './invitation-dialog.component.html',
|
|
})
|
|
export class InvitationDialogComponent implements OnInit {
|
|
@Input({ required: true }) member!: MemberListItemDto;
|
|
@Output() cancelled = new EventEmitter<void>();
|
|
|
|
step: Step = 'generating';
|
|
emailForm!: FormGroup;
|
|
|
|
link = '';
|
|
expiresAt: string | null = null;
|
|
copied = false;
|
|
isSending = false;
|
|
errorMessage = '';
|
|
|
|
get memberName(): string { return memberDisplayName(this.member); }
|
|
|
|
constructor(
|
|
private fb: FormBuilder,
|
|
private invitationApi: InvitationApiService,
|
|
private toast: ToastService,
|
|
) {}
|
|
|
|
ngOnInit(): void {
|
|
this.emailForm = this.fb.group({
|
|
email: [this.member.email ?? '', [Validators.required, Validators.email]],
|
|
});
|
|
|
|
// Auto-generate when the member already has an email; otherwise ask for one first.
|
|
if (this.member.email) {
|
|
this.generate();
|
|
} else {
|
|
this.step = 'needEmail';
|
|
}
|
|
}
|
|
|
|
/** Generate (or re-issue) the link. Uses the form email when the member has none on file. */
|
|
generate(): void {
|
|
if (this.step === 'needEmail') {
|
|
if (this.emailForm.invalid) { this.emailForm.markAllAsTouched(); return; }
|
|
}
|
|
|
|
const email = this.member.email ? undefined : this.emailForm.value.email;
|
|
this.step = 'generating';
|
|
this.errorMessage = '';
|
|
|
|
this.invitationApi.create(this.member.id, email).subscribe({
|
|
next: (result) => {
|
|
this.link = `${window.location.origin}/accept-invitation?token=${result.token}`;
|
|
this.expiresAt = result.expiresAt;
|
|
this.step = 'ready';
|
|
},
|
|
error: (err) => {
|
|
this.errorMessage = err.error?.message ?? 'Failed to create the invitation link.';
|
|
// Fall back to the email step so the admin can supply/correct an address.
|
|
this.step = 'needEmail';
|
|
},
|
|
});
|
|
}
|
|
|
|
copyLink(): void {
|
|
navigator.clipboard.writeText(this.link).then(() => {
|
|
this.copied = true;
|
|
setTimeout(() => (this.copied = false), 2000);
|
|
});
|
|
}
|
|
|
|
sendEmail(): void {
|
|
this.isSending = true;
|
|
this.invitationApi.sendEmail(this.member.id, this.link).subscribe({
|
|
next: () => {
|
|
this.toast.success(`Invitation emailed to ${this.memberName}.`);
|
|
this.isSending = false;
|
|
},
|
|
error: (err) => {
|
|
this.toast.error(err.error?.message ?? 'Failed to send the email.');
|
|
this.isSending = false;
|
|
},
|
|
});
|
|
}
|
|
|
|
onClose(): void {
|
|
this.cancelled.emit();
|
|
}
|
|
}
|