src/app/features/api-client/create-api-client-dialog/create-api-client-dialog.component.ts

Description

Create an API client, with a name and the access (client scopes) the user selects. Closes with the ApiClientCredentialsDialogData of the new API client, if it was created.

Example

Metadata

Relationships

Used by

No results matching.

Depends on

Index

Properties
Methods

Methods

Async create
create()
Returns : any

Properties

Readonly chosenClientId
Type : unknown
Default value : computed(() => this.clientIdValue().trim())

the part of the client ID the user chose, if any

Readonly CLIENT_ID_PREFIX
Type : unknown
Default value : API_CLIENT_ID_PREFIX
Readonly error
Type : unknown
Default value : signal<string | undefined>(undefined)
Readonly form
Type : unknown
Default value : new FormGroup({ name: new FormControl("", { nonNullable: true, validators: [ Validators.required, // the backend rejects a name of only spaces Validators.pattern(/\S/), Validators.maxLength(this.MAX_NAME_LENGTH), ], }), // the part after API_CLIENT_ID_PREFIX; empty to let the backend generate it from the name clientId: new FormControl("", { nonNullable: true, validators: [Validators.pattern(/^[A-Za-z0-9][A-Za-z0-9._-]{0,59}$/)], }), scopes: new FormControl<string[]>([], { nonNullable: true, validators: [Validators.required], }), })
Readonly MAX_NAME_LENGTH
Type : number
Default value : 100
Readonly saving
Type : unknown
Default value : signal(false)
import {
  ChangeDetectionStrategy,
  Component,
  computed,
  inject,
  signal,
} from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop";
import {
  FormControl,
  FormGroup,
  ReactiveFormsModule,
  Validators,
} from "@angular/forms";
import { MatDialogModule, MatDialogRef } from "@angular/material/dialog";
import { MatButtonModule } from "@angular/material/button";
import { MatInputModule } from "@angular/material/input";
import { DialogCloseComponent } from "#src/app/core/common-components/dialog-close/dialog-close.component";
import {
  API_CLIENT_ID_PREFIX,
  ApiClientService,
  errorMessageOf,
  logRequestError,
} from "../api-client.service";
import { ApiClientScopeSelectionComponent } from "../api-client-scope-selection/api-client-scope-selection.component";
import { ApiClientCredentialsDialogData } from "../api-client-credentials-dialog/api-client-credentials-dialog.component";

/**
 * Create an API client, with a name and the access (client scopes) the user selects.
 * Closes with the {@link ApiClientCredentialsDialogData} of the new API client, if it was created.
 */
@Component({
  selector: "app-create-api-client-dialog",
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [
    MatDialogModule,
    MatButtonModule,
    MatInputModule,
    ReactiveFormsModule,
    DialogCloseComponent,
    ApiClientScopeSelectionComponent,
  ],
  templateUrl: "./create-api-client-dialog.component.html",
})
export class CreateApiClientDialogComponent {
  private readonly apiClientService = inject(ApiClientService);
  private readonly dialogRef =
    inject<
      MatDialogRef<
        CreateApiClientDialogComponent,
        ApiClientCredentialsDialogData
      >
    >(MatDialogRef);

  readonly MAX_NAME_LENGTH = 100;

  readonly form = new FormGroup({
    name: new FormControl("", {
      nonNullable: true,
      validators: [
        Validators.required,
        // the backend rejects a name of only spaces
        Validators.pattern(/\S/),
        Validators.maxLength(this.MAX_NAME_LENGTH),
      ],
    }),
    // the part after API_CLIENT_ID_PREFIX; empty to let the backend generate it from the name
    clientId: new FormControl("", {
      nonNullable: true,
      validators: [Validators.pattern(/^[A-Za-z0-9][A-Za-z0-9._-]{0,59}$/)],
    }),
    scopes: new FormControl<string[]>([], {
      nonNullable: true,
      validators: [Validators.required],
    }),
  });

  readonly CLIENT_ID_PREFIX = API_CLIENT_ID_PREFIX;

  readonly saving = signal(false);
  readonly error = signal<string | undefined>(undefined);

  private readonly clientIdValue = toSignal(
    this.form.controls.clientId.valueChanges,
    { initialValue: "" },
  );
  /** the part of the client ID the user chose, if any */
  readonly chosenClientId = computed(() => this.clientIdValue().trim());

  async create() {
    if (this.form.invalid || this.saving()) {
      return;
    }
    this.saving.set(true);
    this.error.set(undefined);

    const { name, clientId, scopes } = this.form.getRawValue();
    try {
      const credentials = await this.apiClientService.createApiClient(
        name.trim(),
        scopes,
        clientId.trim() ? API_CLIENT_ID_PREFIX + clientId.trim() : undefined,
      );
      this.dialogRef.close({ credentials, scopes });
    } catch (err) {
      logRequestError("Could not create API client", err);
      this.error.set(errorMessageOf(err));
    } finally {
      this.saving.set(false);
    }
  }
}
<h2 mat-dialog-title i18n>New API client</h2>
<app-dialog-close mat-dialog-close></app-dialog-close>

<mat-dialog-content>
  <p i18n>
    Create credentials for an external system, so that it can access data of
    this system through the API.
  </p>

  <form [formGroup]="form" (ngSubmit)="create()" class="flex-column">
    <mat-form-field>
      <mat-label i18n>Name</mat-label>
      <input
        matInput
        formControlName="name"
        [maxlength]="MAX_NAME_LENGTH"
        cdkFocusInitial
      />
      <mat-hint i18n>e.g. the name of the external system</mat-hint>
    </mat-form-field>

    <mat-form-field>
      <mat-label i18n>Client ID (optional)</mat-label>
      <input matInput formControlName="clientId" />
      @if (chosenClientId(); as clientId) {
        <mat-hint i18n
          >The client ID will be "{{ CLIENT_ID_PREFIX
          }}{{ clientId }}".</mat-hint
        >
      } @else {
        <mat-hint i18n>
          If the external system expects a certain client ID. Leave empty to
          generate one from the name.
        </mat-hint>
      }
      @if (form.controls.clientId.hasError("pattern")) {
        <mat-error i18n>
          Only letters, digits, ".", "_" and "-" (starting with a letter or
          digit, 60 characters at most)
        </mat-error>
      }
    </mat-form-field>

    <h3 i18n>Access</h3>
    <app-api-client-scope-selection
      [control]="form.controls.scopes"
    ></app-api-client-scope-selection>

    @if (error(); as error) {
      <p class="color-error" role="alert">{{ error }}</p>
    }
  </form>
</mat-dialog-content>

<mat-dialog-actions>
  <button
    mat-raised-button
    color="accent"
    type="button"
    (click)="create()"
    [disabled]="form.invalid || saving()"
    i18n="Button label"
  >
    Create
  </button>
  <button mat-button mat-dialog-close i18n="Button label">Cancel</button>
</mat-dialog-actions>
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""