feat: add CreateUserAccountDialogComponent with temp-password reveal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+67
@@ -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>
|
||||||
+92
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user