Add init link.

This commit is contained in:
Chris Chen
2026-06-24 10:53:13 -07:00
parent e88ea7917f
commit e53cea7a82
20 changed files with 971 additions and 11 deletions
@@ -0,0 +1,106 @@
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();
}
}