feat: add CreateUserAccountDialogComponent with temp-password reveal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Chen
2026-05-27 14:17:15 -07:00
parent 32e47e4566
commit 07e0270599
2 changed files with 159 additions and 0 deletions
@@ -0,0 +1,67 @@
<kendo-dialog title="Create User Account" (close)="onCancel()" [minWidth]="480" [width]="520">
<!-- STEP 1: Form -->
<ng-container *ngIf="step === 'form'">
<p class="k-mb-4">Creating account for <strong>{{ memberName }}</strong></p>
<form [formGroup]="form" (ngSubmit)="onSubmit()" class="k-form k-form-vertical">
<kendo-formfield>
<kendo-label text="Login Email *"></kendo-label>
<kendo-textbox formControlName="email" type="email"></kendo-textbox>
<kendo-formerror *ngIf="form.get('email')?.errors?.['required']">Email is required.</kendo-formerror>
<kendo-formerror *ngIf="form.get('email')?.errors?.['email']">Invalid email address.</kendo-formerror>
</kendo-formfield>
<kendo-formfield class="k-mt-3">
<kendo-label text="Roles *"></kendo-label>
<kendo-multiselect
formControlName="roles"
[data]="roleOptions"
placeholder="Select role(s)">
</kendo-multiselect>
</kendo-formfield>
<kendo-formfield class="k-mt-3">
<kendo-label text="Language"></kendo-label>
<kendo-dropdownlist
formControlName="languagePreference"
[data]="langOptions" textField="text" valueField="value">
</kendo-dropdownlist>
</kendo-formfield>
<p *ngIf="errorMessage" class="k-color-error k-mt-3">{{ errorMessage }}</p>
</form>
<kendo-dialog-actions>
<button kendoButton (click)="onCancel()">Cancel</button>
<button kendoButton themeColor="primary" (click)="onSubmit()" [disabled]="isLoading">
<span *ngIf="isLoading">...</span>
Create Account
</button>
</kendo-dialog-actions>
</ng-container>
<!-- STEP 2: Success — show temp password -->
<ng-container *ngIf="step === 'success'">
<div class="k-text-center k-p-4">
<p class="k-font-size-lg k-mb-2">Account created!</p>
<p class="k-mb-4">Share this temporary password with <strong>{{ memberName }}</strong>.</p>
<div class="k-d-flex k-justify-content-center k-align-items-center k-gap-2 k-mb-3">
<code class="k-font-size-lg k-border k-p-2 k-rounded">{{ tempPassword }}</code>
<button kendoButton (click)="copyPassword()">{{ copied ? 'Copied!' : 'Copy' }}</button>
</div>
<p class="k-color-warning k-font-weight-bold">
Warning: This password will not be shown again.
</p>
</div>
<kendo-dialog-actions>
<button kendoButton themeColor="primary" (click)="onDone()">Done</button>
</kendo-dialog-actions>
</ng-container>
</kendo-dialog>
@@ -0,0 +1,92 @@
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 { DropDownsModule } from '@progress/kendo-angular-dropdowns';
import { ButtonsModule } from '@progress/kendo-angular-buttons';
import { IndicatorsModule } from '@progress/kendo-angular-indicators';
import { MemberListItemDto, memberDisplayName } from '../../models/member.model';
import { CreateUserRequest, CreateUserResult, ALL_ROLES } from '../../../users/models/user.model';
import { UserApiService } from '../../../users/services/user-api.service';
@Component({
selector: 'app-create-user-dialog',
standalone: true,
imports: [
CommonModule, ReactiveFormsModule, DialogsModule, InputsModule,
LabelModule, DropDownsModule, ButtonsModule, IndicatorsModule
],
templateUrl: './create-user-dialog.component.html',
})
export class CreateUserDialogComponent implements OnInit {
@Input({ required: true }) member!: MemberListItemDto;
@Output() created = new EventEmitter<void>();
@Output() cancelled = new EventEmitter<void>();
form!: FormGroup;
step: 'form' | 'success' = 'form';
tempPassword = '';
copied = false;
isLoading = false;
errorMessage = '';
readonly roleOptions: string[] = [...ALL_ROLES];
readonly langOptions = [
{ text: 'English', value: 'en' },
{ text: '中文', value: 'zh-TW' },
];
get memberName(): string { return memberDisplayName(this.member); }
constructor(private fb: FormBuilder, private userApi: UserApiService) {}
ngOnInit(): void {
this.form = this.fb.group({
email: [this.member.email ?? '', [Validators.required, Validators.email]],
roles: [['member'], Validators.required],
languagePreference: ['en'],
});
}
onSubmit(): void {
if (this.form.invalid) { this.form.markAllAsTouched(); return; }
this.isLoading = true;
this.errorMessage = '';
const request: CreateUserRequest = {
memberId: this.member.id,
email: this.form.value.email,
roles: this.form.value.roles,
languagePreference: this.form.value.languagePreference,
};
this.userApi.createUser(request).subscribe({
next: (result: CreateUserResult) => {
this.tempPassword = result.tempPassword;
this.step = 'success';
this.isLoading = false;
},
error: (err: any) => {
this.errorMessage = err.error?.message ?? 'Failed to create account.';
this.isLoading = false;
},
});
}
copyPassword(): void {
navigator.clipboard.writeText(this.tempPassword).then(() => {
this.copied = true;
setTimeout(() => (this.copied = false), 2000);
});
}
onDone(): void {
this.created.emit();
}
onCancel(): void {
this.cancelled.emit();
}
}