frontend: add loading state handling in DevelopersComponent

This commit is contained in:
juancarmore 2025-07-02 20:33:09 +02:00
parent e1bf07532c
commit a3560ee845
3 changed files with 146 additions and 125 deletions

View File

@ -7,48 +7,65 @@
<p class="subtitle">Everyting you need to embed OpenVidu Meet in your applications.</p> <p class="subtitle">Everyting you need to embed OpenVidu Meet in your applications.</p>
</div> </div>
<div class="page-content"> @if (isLoading()) {
<!-- API KEY Section --> <div class="ov-page-loading">
<mat-card class="section-card api-key-section"> <div class="loading-content">
<mat-card-header> <div class="loading-header">
<div mat-card-avatar> <div class="loading-title">
<mat-icon class="section-icon">vpn_key</mat-icon> <mat-icon class="ov-settings-icon loading-icon">settings</mat-icon>
</div> <h1>Loading Settings</h1>
<mat-card-title>API KEY</mat-card-title>
<mat-card-subtitle>Generate and manage your API key for REST API access</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
@if (apiKeyData()) {
<div class="api-key-display">
<mat-form-field subscriptSizing="dynamic" appearance="outline" class="api-key-field">
<mat-label>API Key</mat-label>
<input
matInput
readonly
[type]="showApiKey() ? 'text' : 'password'"
[value]="apiKeyData()!.key"
/>
<button
mat-icon-button
matSuffix
(click)="toggleApiKeyVisibility()"
matTooltip="Toggle visibility"
class="toggle-visibility-button"
>
<mat-icon>{{ showApiKey() ? 'visibility_off' : 'visibility' }}</mat-icon>
</button>
</mat-form-field>
<div class="api-key-actions">
<button mat-button (click)="copyApiKey()" class="copy-button">
<mat-icon>content_copy</mat-icon>
Copy
</button>
</div>
</div> </div>
<p class="loading-subtitle">Please wait while we fetch your settings...</p>
</div>
<!-- <div class="api-key-info"> <div class="loading-spinner-container">
<mat-spinner diameter="48"></mat-spinner>
</div>
</div>
</div>
} @else {
<div class="page-content">
<!-- API KEY Section -->
<mat-card class="section-card api-key-section">
<mat-card-header>
<div mat-card-avatar>
<mat-icon class="section-icon">vpn_key</mat-icon>
</div>
<mat-card-title>API KEY</mat-card-title>
<mat-card-subtitle>Generate and manage your API key for REST API access</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
@if (apiKeyData()) {
<div class="api-key-display">
<mat-form-field subscriptSizing="dynamic" appearance="outline" class="api-key-field">
<mat-label>API Key</mat-label>
<input
matInput
readonly
[type]="showApiKey() ? 'text' : 'password'"
[value]="apiKeyData()!.key"
/>
<button
mat-icon-button
matSuffix
(click)="toggleApiKeyVisibility()"
matTooltip="Toggle visibility"
class="toggle-visibility-button"
>
<mat-icon>{{ showApiKey() ? 'visibility_off' : 'visibility' }}</mat-icon>
</button>
</mat-form-field>
<div class="api-key-actions">
<button mat-button (click)="copyApiKey()" class="copy-button">
<mat-icon>content_copy</mat-icon>
Copy
</button>
</div>
</div>
<!-- <div class="api-key-info">
<p class="info-text"> <p class="info-text">
<mat-icon class="info-icon">info</mat-icon> <mat-icon class="info-icon">info</mat-icon>
Generated on: {{ apiKeyData()!.creationDate | date: 'medium' }} Generated on: {{ apiKeyData()!.creationDate | date: 'medium' }}
@ -58,71 +75,70 @@
Keep your API key secure. Do not share it publicly or expose it in client-side code. Keep your API key secure. Do not share it publicly or expose it in client-side code.
</p> </p>
</div> --> </div> -->
} @else { } @else {
<div class="no-api-key"> <div class="no-api-key">
<h3>No API Key Generated</h3> <h3>No API Key Generated</h3>
<p>Generate an API key to access OpenVidu Meet REST API endpoints.</p> <p>Generate an API key to access OpenVidu Meet REST API endpoints.</p>
<button mat-raised-button class (click)="generateApiKey()"> <button mat-raised-button class (click)="generateApiKey()">
<mat-icon>vpn_key</mat-icon> <mat-icon>vpn_key</mat-icon>
Generate API Key Generate API Key
</button>
</div>
}
</mat-card-content>
@if (apiKeyData()) {
<mat-card-actions>
<button mat-raised-button color="primary" (click)="regenerateApiKey()">
<mat-icon>refresh</mat-icon>
Regenerate Key
</button> </button>
</div> <button mat-stroked-button id="revoke-key-btn" (click)="revokeApiKey()">
<mat-icon>delete</mat-icon>
Revoke Key
</button>
</mat-card-actions>
} }
</mat-card-content> </mat-card>
@if (apiKeyData()) { <!-- WEBHOOKS Section -->
<mat-card-actions> <mat-card class="section-card webhooks-section">
<button mat-raised-button color="primary" (click)="regenerateApiKey()"> <mat-card-header>
<mat-icon>refresh</mat-icon> <div mat-card-avatar>
Regenerate Key <mat-icon class="section-icon">webhook</mat-icon>
</button>
<button mat-stroked-button id="revoke-key-btn" (click)="revokeApiKey()">
<mat-icon>delete</mat-icon>
Revoke Key
</button>
</mat-card-actions>
}
</mat-card>
<!-- WEBHOOKS Section -->
<mat-card class="section-card webhooks-section">
<mat-card-header>
<div mat-card-avatar>
<mat-icon class="section-icon">webhook</mat-icon>
</div>
<mat-card-title>WEBHOOKS</mat-card-title>
<mat-card-subtitle>Configure webhook notifications for real-time event updates</mat-card-subtitle>
</mat-card-header>
<mat-card-content class="ov-mt-sm">
<form [formGroup]="webhookForm" class="webhook-form">
<!-- Enable/Disable Toggle -->
<h4 class="webhook-section-title">Webhook Notifications</h4>
<div class="webhook-toggle ov-mt-xs">
<span>Enable webhook notifications</span>
<mat-slide-toggle formControlName="isEnabled"></mat-slide-toggle>
</div> </div>
<mat-card-title>WEBHOOKS</mat-card-title>
<mat-card-subtitle>Configure webhook notifications for real-time event updates</mat-card-subtitle>
</mat-card-header>
<!-- Webhook URL --> <mat-card-content class="ov-mt-sm">
<h4 class="webhook-section-title">Webhook URL</h4> <form [formGroup]="webhookForm" class="webhook-form">
<p class="webhook-description ov-mt-xs"> <!-- Enable/Disable Toggle -->
Enter the URL where you want to receive webhook notifications. <h4 class="webhook-section-title">Webhook Notifications</h4>
</p> <div class="webhook-toggle ov-mt-xs">
<mat-form-field appearance="outline" class="full-width"> <span>Enable webhook notifications</span>
<mat-label>Webhook URL</mat-label> <mat-slide-toggle formControlName="isEnabled"></mat-slide-toggle>
<input matInput formControlName="url" placeholder="https://your-server.com/webhook" /> </div>
<mat-icon matSuffix>link</mat-icon>
@if (webhookForm.get('url')?.hasError('required')) {
<mat-error>Webhook URL is required</mat-error>
}
@if (webhookForm.get('url')?.hasError('pattern')) {
<mat-error>Please enter a valid HTTP(S) URL</mat-error>
}
</mat-form-field>
<!-- Webhook URL -->
<h4 class="webhook-section-title">Webhook URL</h4>
<p class="webhook-description ov-mt-xs">
Enter the URL where you want to receive webhook notifications.
</p>
<mat-form-field appearance="outline" class="full-width">
<mat-label>Webhook URL</mat-label>
<input matInput formControlName="url" placeholder="https://your-server.com/webhook" />
<mat-icon matSuffix>link</mat-icon>
@if (webhookForm.get('url')?.hasError('required')) {
<mat-error>Webhook URL is required</mat-error>
}
@if (webhookForm.get('url')?.hasError('pattern')) {
<mat-error>Please enter a valid HTTP(S) URL</mat-error>
}
</mat-form-field>
<!-- Event Configuration --> <!-- Event Configuration -->
<!-- <div class="events-section"> <!-- <div class="events-section">
<h4>Event Types</h4> <h4>Event Types</h4>
<p class="events-description">Choose which events should trigger webhook notifications</p> <p class="events-description">Choose which events should trigger webhook notifications</p>
@ -158,24 +174,29 @@
</div> </div>
</div> </div>
</div> --> </div> -->
</form> </form>
</mat-card-content> </mat-card-content>
<mat-card-actions> <mat-card-actions>
<button <button
mat-raised-button mat-raised-button
color="primary" color="primary"
(click)="saveWebhookConfig()" (click)="saveWebhookConfig()"
[disabled]="webhookForm.invalid" [disabled]="webhookForm.invalid"
> >
<mat-icon>save</mat-icon> <mat-icon>save</mat-icon>
Save Configuration Save Configuration
</button> </button>
<button mat-stroked-button (click)="testWebhook()" [disabled]="!webhookForm.get('url')?.value"> <button
<mat-icon>send</mat-icon> mat-stroked-button
Test Webhook (click)="testWebhook()"
</button> [disabled]="webhookForm.get('url')?.disabled || webhookForm.get('url')?.invalid"
</mat-card-actions> >
</mat-card> <mat-icon>send</mat-icon>
</div> Test Webhook
</button>
</mat-card-actions>
</mat-card>
</div>
}
</div> </div>

View File

@ -1,15 +1,13 @@
import { Clipboard } from '@angular/cdk/clipboard'; import { Clipboard } from '@angular/cdk/clipboard';
import { CommonModule } from '@angular/common';
import { Component, OnInit, signal } from '@angular/core'; import { Component, OnInit, signal } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { AuthService, GlobalPreferencesService, NotificationService } from '@lib/services'; import { AuthService, GlobalPreferencesService, NotificationService } from '@lib/services';
import { MeetApiKey } from '@lib/typings/ce'; import { MeetApiKey } from '@lib/typings/ce';
@ -18,22 +16,22 @@ import { MeetApiKey } from '@lib/typings/ce';
selector: 'ov-developers-settings', selector: 'ov-developers-settings',
standalone: true, standalone: true,
imports: [ imports: [
CommonModule,
MatCardModule, MatCardModule,
MatButtonModule, MatButtonModule,
MatIconModule, MatIconModule,
MatInputModule, MatInputModule,
MatFormFieldModule, MatFormFieldModule,
MatSlideToggleModule, MatSlideToggleModule,
MatSnackBarModule,
MatTooltipModule, MatTooltipModule,
MatDividerModule, ReactiveFormsModule,
ReactiveFormsModule MatProgressSpinnerModule
], ],
templateUrl: './developers.component.html', templateUrl: './developers.component.html',
styleUrl: './developers.component.scss' styleUrl: './developers.component.scss'
}) })
export class DevelopersSettingsComponent implements OnInit { export class DevelopersSettingsComponent implements OnInit {
isLoading = signal(true);
apiKeyData = signal<MeetApiKey | undefined>(undefined); apiKeyData = signal<MeetApiKey | undefined>(undefined);
showApiKey = signal(false); showApiKey = signal(false);
@ -66,8 +64,10 @@ export class DevelopersSettingsComponent implements OnInit {
} }
async ngOnInit() { async ngOnInit() {
this.isLoading.set(true);
await this.loadApiKeyData(); await this.loadApiKeyData();
await this.loadWebhookConfig(); await this.loadWebhookConfig();
this.isLoading.set(false);
} }
// ===== API KEY METHODS ===== // ===== API KEY METHODS =====

View File

@ -31,7 +31,7 @@ import { AuthMode } from '@lib/typings/ce';
styleUrl: './users-permissions.component.scss' styleUrl: './users-permissions.component.scss'
}) })
export class UsersPermissionsComponent implements OnInit { export class UsersPermissionsComponent implements OnInit {
isLoading = signal(false); isLoading = signal(true);
adminCredentialsForm = new FormGroup({ adminCredentialsForm = new FormGroup({
adminUsername: new FormControl({ value: '', disabled: true }, [Validators.required]), adminUsername: new FormControl({ value: '', disabled: true }, [Validators.required]),