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">
<section class="step-content ov-surface">
@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>
@if (isCreatingRoom()) {
<!-- Room Creation Loading State -->
<div class="room-creation-loading">
<div class="loading-content">
<div class="loading-header">
<div class="loading-title">
<mat-icon class="ov-room-icon loading-icon">video_chat</mat-icon>
<h2>{{ editMode ? 'Updating Room' : 'Creating Room' }}</h2>
</div>
<p class="loading-subtitle">
{{
editMode
? 'Please wait while we update your room settings...'
: 'Please wait while we set up your video room...'
}}
</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>

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
@include ov-tablet-down {
.wizard-container {
@ -90,6 +149,20 @@
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 {

View File

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