frontend: add loading state for room creation with spinner and loading messages

This commit is contained in:
Carlos Santos 2025-07-24 17:33:48 +02:00
parent 10b3531cf3
commit 275d15f68f
3 changed files with 128 additions and 21 deletions

View File

@ -18,21 +18,45 @@
<main class="wizard-content"> <main class="wizard-content">
<section class="step-content ov-surface"> <section class="step-content ov-surface">
@switch (currentStep()?.id) { @if (isCreatingRoom()) {
@case ('basic') { <!-- Room Creation Loading State -->
<ov-room-wizard-basic-info></ov-room-wizard-basic-info> <div class="room-creation-loading">
} <div class="loading-content">
@case ('recording') { <div class="loading-header">
<ov-recording-preferences></ov-recording-preferences> <div class="loading-title">
} <mat-icon class="ov-room-icon loading-icon">video_chat</mat-icon>
@case ('recordingTrigger') { <h2>{{ editMode ? 'Updating Room' : 'Creating Room' }}</h2>
<ov-recording-trigger></ov-recording-trigger> </div>
} <p class="loading-subtitle">
@case ('recordingLayout') { {{
<ov-recording-layout></ov-recording-layout> editMode
} ? 'Please wait while we update your room settings...'
@case ('preferences') { : 'Please wait while we set up your video room...'
<ov-room-preferences></ov-room-preferences> }}
</p>
</div>
<div class="loading-spinner-container">
<mat-spinner diameter="48"></mat-spinner>
</div>
</div>
</div>
} @else {
@switch (currentStep()?.id) {
@case ('basic') {
<ov-room-wizard-basic-info></ov-room-wizard-basic-info>
}
@case ('recording') {
<ov-recording-preferences></ov-recording-preferences>
}
@case ('recordingTrigger') {
<ov-recording-trigger></ov-recording-trigger>
}
@case ('recordingLayout') {
<ov-recording-layout></ov-recording-layout>
}
@case ('preferences') {
<ov-room-preferences></ov-room-preferences>
}
} }
} }
</section> </section>

View File

@ -77,6 +77,65 @@
} }
} }
// Room Creation Loading State
.room-creation-loading {
@include ov-flex-center;
flex-direction: column;
height: 100%;
min-height: 400px;
text-align: center;
padding: var(--ov-meet-spacing-xl);
.loading-content {
@include ov-flex-center;
flex-direction: column;
gap: var(--ov-meet-spacing-xl);
width: 100%;
.loading-header {
margin-bottom: var(--ov-meet-spacing-lg);
.loading-title {
@include ov-flex-center;
gap: var(--ov-meet-spacing-md);
margin-bottom: var(--ov-meet-spacing-md);
.loading-icon {
@include ov-icon(xl);
animation: pulse 2s ease-in-out infinite;
}
h2 {
margin: 0;
font-size: var(--ov-meet-font-size-xl);
font-weight: var(--ov-meet-font-weight-medium);
color: var(--ov-meet-text-primary);
}
}
.loading-subtitle {
font-size: var(--ov-meet-font-size-md);
color: var(--ov-meet-text-secondary);
margin: 0;
animation: fadeIn 1s ease-out 0.5s both;
}
}
.loading-spinner-container {
mat-spinner {
margin: 0 auto;
::ng-deep {
.mdc-circular-progress__determinate-circle,
.mdc-circular-progress__indeterminate-circle-graphic {
stroke: var(--ov-meet-color-primary);
}
}
}
}
}
}
// Responsive design using the system mixins // Responsive design using the system mixins
@include ov-tablet-down { @include ov-tablet-down {
.wizard-container { .wizard-container {
@ -90,6 +149,20 @@
font-size: var(--ov-meet-font-size-xl); font-size: var(--ov-meet-font-size-xl);
} }
} }
.room-creation-loading {
padding: var(--ov-meet-spacing-lg);
.loading-content {
.loading-header {
.loading-title {
h2 {
font-size: var(--ov-meet-font-size-lg);
}
}
}
}
}
} }
@include ov-mobile-down { @include ov-mobile-down {

View File

@ -1,10 +1,11 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, computed, OnInit, Signal } from '@angular/core'; import { Component, computed, OnInit, Signal, signal } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { StepIndicatorComponent, WizardNavComponent } from '@lib/components'; import { SpinnerComponent, StepIndicatorComponent, WizardNavComponent } from '@lib/components';
import { WizardNavigationConfig, WizardStep } from '@lib/models'; import { WizardNavigationConfig, WizardStep } from '@lib/models';
import { NavigationService, NotificationService, RoomService, RoomWizardStateService } from '@lib/services'; import { NavigationService, NotificationService, RoomService, RoomWizardStateService } from '@lib/services';
import { MeetRoomOptions } from '@lib/typings/ce'; import { MeetRoomOptions } from '@lib/typings/ce';
@ -21,8 +22,10 @@ import { RoomPreferencesComponent } from './steps/room-preferences/room-preferen
CommonModule, CommonModule,
StepIndicatorComponent, StepIndicatorComponent,
WizardNavComponent, WizardNavComponent,
SpinnerComponent,
MatButtonModule, MatButtonModule,
MatIconModule, MatIconModule,
MatProgressSpinnerModule,
MatSlideToggleModule, MatSlideToggleModule,
RoomWizardBasicInfoComponent, RoomWizardBasicInfoComponent,
RecordingPreferencesComponent, RecordingPreferencesComponent,
@ -37,6 +40,7 @@ export class RoomWizardComponent implements OnInit {
editMode: boolean = false; editMode: boolean = false;
roomId?: string; roomId?: string;
existingRoomData?: MeetRoomOptions; existingRoomData?: MeetRoomOptions;
isCreatingRoom = signal(false);
steps: Signal<WizardStep[]>; steps: Signal<WizardStep[]>;
currentStep: Signal<WizardStep | undefined>; currentStep: Signal<WizardStep | undefined>;
@ -114,21 +118,27 @@ export class RoomWizardComponent implements OnInit {
const roomOptions = this.wizardService.roomOptions(); const roomOptions = this.wizardService.roomOptions();
console.log('Wizard completed with data:', roomOptions); console.log('Wizard completed with data:', roomOptions);
// Activate loading state
this.isCreatingRoom.set(true);
try { try {
if (this.editMode && this.roomId && roomOptions.preferences) { if (this.editMode && this.roomId && roomOptions.preferences) {
await this.roomService.updateRoom(this.roomId, roomOptions.preferences); await this.roomService.updateRoom(this.roomId, roomOptions.preferences);
await this.navigationService.navigateTo('rooms', undefined, true);
this.notificationService.showSnackbar('Room updated successfully'); this.notificationService.showSnackbar('Room updated successfully');
} else { } else {
// Create new room // Create new room
await this.roomService.createRoom(roomOptions); const { moderatorRoomUrl } = await this.roomService.createRoom(roomOptions);
this.notificationService.showSnackbar('Room created successfully'); await this.navigationService.redirectTo(moderatorRoomUrl);
} }
await this.navigationService.navigateTo('rooms', undefined, true);
} catch (error) { } catch (error) {
const errorMessage = `Failed to ${this.editMode ? 'update' : 'create'} room`; const errorMessage = `Failed to ${this.editMode ? 'update' : 'create'} room`;
this.notificationService.showSnackbar(errorMessage); this.notificationService.showSnackbar(errorMessage);
console.error(errorMessage, error); console.error(errorMessage, error);
} finally {
this.wizardService.resetWizard();
// Deactivate loading state
this.isCreatingRoom.set(false);
} }
} }
} }