159 lines
5.5 KiB
TypeScript
159 lines
5.5 KiB
TypeScript
import { Component, OnInit } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
import { ActivatedRoute, Router } from '@angular/router';
|
|
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 { AuthService } from '../../shared/services/auth.service';
|
|
import {
|
|
passwordStrengthValidator,
|
|
passwordMatchValidator,
|
|
} from '../account/validators/password.validators';
|
|
|
|
type Step = 'loading' | 'invalid' | 'form';
|
|
|
|
@Component({
|
|
selector: 'app-accept-invitation',
|
|
standalone: true,
|
|
imports: [
|
|
CommonModule, ReactiveFormsModule,
|
|
InputsModule, LabelModule, ButtonsModule, IndicatorsModule,
|
|
],
|
|
template: `
|
|
<div class="min-h-screen flex items-center justify-center p-4">
|
|
<div class="w-full max-w-md rounded-lg border border-gray-200 bg-white p-6 shadow-sm">
|
|
|
|
<h1 class="text-xl font-semibold mb-1">River Of Life Christian Church</h1>
|
|
|
|
<!-- Validating the link -->
|
|
<ng-container *ngIf="step === 'loading'">
|
|
<div class="text-center py-6">
|
|
<kendo-loader></kendo-loader>
|
|
<p class="mt-2 text-gray-600">Checking your invitation…</p>
|
|
</div>
|
|
</ng-container>
|
|
|
|
<!-- Invalid / expired link -->
|
|
<ng-container *ngIf="step === 'invalid'">
|
|
<p class="text-base font-medium mb-2">This invitation can't be used</p>
|
|
<p class="text-gray-600 mb-4">{{ invalidMessage }}</p>
|
|
<button kendoButton themeColor="primary" (click)="goToLogin()">Go to sign in</button>
|
|
</ng-container>
|
|
|
|
<!-- Set password form -->
|
|
<ng-container *ngIf="step === 'form'">
|
|
<p class="text-gray-600 mb-4">
|
|
Welcome<span *ngIf="memberName">, <strong>{{ memberName }}</strong></span>. Set a password to
|
|
finish creating your account and sign in.
|
|
</p>
|
|
|
|
<form [formGroup]="form" class="k-form k-form-vertical" (ngSubmit)="onSubmit()">
|
|
<div class="grid grid-cols-1 gap-y-3">
|
|
|
|
<kendo-formfield>
|
|
<kendo-label text="New Password *"></kendo-label>
|
|
<kendo-textbox formControlName="newPassword" type="password" [clearButton]="false"></kendo-textbox>
|
|
<kendo-formerror *ngIf="form.get('newPassword')?.errors?.['required']">Required.</kendo-formerror>
|
|
<kendo-formerror *ngIf="form.get('newPassword')?.errors?.['passwordStrength']">
|
|
Must be at least 8 characters with an uppercase letter, a lowercase letter,
|
|
a digit, and a special character.
|
|
</kendo-formerror>
|
|
</kendo-formfield>
|
|
|
|
<kendo-formfield>
|
|
<kendo-label text="Confirm Password *"></kendo-label>
|
|
<kendo-textbox formControlName="confirmPassword" type="password" [clearButton]="false"></kendo-textbox>
|
|
<kendo-formerror *ngIf="form.get('confirmPassword')?.errors?.['required']">Required.</kendo-formerror>
|
|
<kendo-formerror *ngIf="form.errors?.['mismatch'] && form.get('confirmPassword')?.touched">
|
|
Passwords do not match.
|
|
</kendo-formerror>
|
|
</kendo-formfield>
|
|
|
|
<p *ngIf="errorMessage" class="k-color-error">{{ errorMessage }}</p>
|
|
|
|
<div class="mt-2">
|
|
<button kendoButton themeColor="primary" type="submit" [disabled]="form.invalid || submitting">
|
|
<span *ngIf="submitting">…</span>
|
|
Set password & sign in
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
</form>
|
|
</ng-container>
|
|
|
|
</div>
|
|
</div>
|
|
`,
|
|
})
|
|
export class AcceptInvitationComponent implements OnInit {
|
|
step: Step = 'loading';
|
|
form: FormGroup;
|
|
submitting = false;
|
|
memberName: string | null = null;
|
|
invalidMessage = 'This invitation link is invalid or has already been used.';
|
|
errorMessage = '';
|
|
|
|
private token = '';
|
|
|
|
constructor(
|
|
private fb: FormBuilder,
|
|
private auth: AuthService,
|
|
private route: ActivatedRoute,
|
|
private router: Router,
|
|
) {
|
|
this.form = this.fb.group(
|
|
{
|
|
newPassword: ['', [Validators.required, passwordStrengthValidator()]],
|
|
confirmPassword: ['', [Validators.required]],
|
|
},
|
|
{ validators: passwordMatchValidator() },
|
|
);
|
|
}
|
|
|
|
ngOnInit(): void {
|
|
this.token = this.route.snapshot.queryParamMap.get('token') ?? '';
|
|
if (!this.token) {
|
|
this.step = 'invalid';
|
|
return;
|
|
}
|
|
|
|
this.auth.validateInvitation(this.token).subscribe({
|
|
next: (result) => {
|
|
if (result.valid) {
|
|
this.memberName = result.memberName ?? null;
|
|
this.step = 'form';
|
|
} else {
|
|
this.invalidMessage = result.expired
|
|
? 'This invitation link has expired. Please ask for a new one.'
|
|
: 'This invitation link is invalid or has already been used.';
|
|
this.step = 'invalid';
|
|
}
|
|
},
|
|
error: () => { this.step = 'invalid'; },
|
|
});
|
|
}
|
|
|
|
onSubmit(): void {
|
|
if (this.form.invalid) { this.form.markAllAsTouched(); return; }
|
|
this.submitting = true;
|
|
this.errorMessage = '';
|
|
|
|
this.auth.acceptInvitation(this.token, this.form.value.newPassword).subscribe({
|
|
next: () => {
|
|
this.router.navigate(['/user-portal/dashboard']);
|
|
},
|
|
error: (err) => {
|
|
this.errorMessage = err.error?.message ?? 'Could not set your password. The link may have expired.';
|
|
this.submitting = false;
|
|
},
|
|
});
|
|
}
|
|
|
|
goToLogin(): void {
|
|
this.router.navigate(['/login']);
|
|
}
|
|
}
|