frontend: Implement basic room creation component and integrate with room wizard
- Added RoomBasicCreationComponent for creating a room with optional name prefix. - Integrated basic creation into RoomWizardComponent, allowing users to switch to advanced mode. - Updated wizard navigation to include 'back' functionality and adjusted visibility of navigation buttons. - Created RoomDetailsComponent for configuring room details, including auto-deletion date and time. - Enhanced styling for new components and ensured responsive design. - Updated wizard state service to manage new steps and handle edit mode appropriately.
This commit is contained in:
parent
2c87a3b8d8
commit
91c9690953
@ -14,6 +14,20 @@
|
||||
</button>
|
||||
}
|
||||
|
||||
@if (config.showBack) {
|
||||
<!-- Back Button -->
|
||||
<button
|
||||
mat-stroked-button
|
||||
class="back-btn"
|
||||
id="wizard-back-btn"
|
||||
(click)="onBack()"
|
||||
[attr.aria-label]="'Go back'"
|
||||
>
|
||||
<mat-icon class="leading-icon">arrow_back</mat-icon>
|
||||
{{ backButtonText }}
|
||||
</button>
|
||||
}
|
||||
|
||||
<div class="spacer"></div>
|
||||
|
||||
<!-- Navigation Group -->
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
.cancel-btn, .back-btn {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&.cancel-btn {
|
||||
&.cancel-btn, &.back-btn {
|
||||
color: var(--ov-meet-text-secondary);
|
||||
border-color: var(--ov-meet-border-color-strong);
|
||||
|
||||
@ -127,7 +127,7 @@
|
||||
flex-direction: column;
|
||||
gap: var(--ov-meet-spacing-md);
|
||||
|
||||
.cancel-btn {
|
||||
.cancel-btn, .back-btn {
|
||||
margin-right: 0;
|
||||
order: 3;
|
||||
width: 100%;
|
||||
@ -171,7 +171,7 @@
|
||||
}
|
||||
|
||||
&.loading {
|
||||
button:not(.cancel-btn) {
|
||||
button:not(.cancel-btn):not(.back-btn) {
|
||||
pointer-events: none;
|
||||
opacity: 0.7;
|
||||
|
||||
|
||||
@ -17,7 +17,8 @@ export class WizardNavComponent {
|
||||
@Input() config: WizardNavigationConfig = {
|
||||
showPrevious: false,
|
||||
showNext: true,
|
||||
showCancel: true,
|
||||
showCancel: false,
|
||||
showBack: true,
|
||||
showFinish: false,
|
||||
showSkipAndFinish: false,
|
||||
disableFinish: false,
|
||||
@ -27,6 +28,8 @@ export class WizardNavComponent {
|
||||
finishLabel: 'Finish'
|
||||
};
|
||||
|
||||
@Input() backButtonText: string = 'Back';
|
||||
|
||||
/**
|
||||
* Current step identifier for context
|
||||
*/
|
||||
@ -38,6 +41,7 @@ export class WizardNavComponent {
|
||||
@Output() previous = new EventEmitter<WizardNavigationEvent>();
|
||||
@Output() next = new EventEmitter<WizardNavigationEvent>();
|
||||
@Output() cancel = new EventEmitter<WizardNavigationEvent>();
|
||||
@Output() back = new EventEmitter<WizardNavigationEvent>();
|
||||
@Output() finish = new EventEmitter<WizardNavigationEvent>();
|
||||
|
||||
/**
|
||||
@ -78,6 +82,16 @@ export class WizardNavComponent {
|
||||
this.navigate.emit(event);
|
||||
}
|
||||
|
||||
onBack() {
|
||||
if (!this.config.showBack) return;
|
||||
const event: WizardNavigationEvent = {
|
||||
action: 'back',
|
||||
currentStepIndex: this.currentStepId
|
||||
};
|
||||
this.back.emit(event);
|
||||
this.navigate.emit(event);
|
||||
}
|
||||
|
||||
onFinish() {
|
||||
if (!this.config.showFinish) return;
|
||||
|
||||
|
||||
@ -19,7 +19,10 @@ export interface WizardNavigationConfig {
|
||||
// Button visibility flags
|
||||
showPrevious: boolean;
|
||||
showNext: boolean;
|
||||
// Cancel button visibility
|
||||
showCancel: boolean;
|
||||
// Used for going back to the previous page
|
||||
showBack: boolean;
|
||||
showFinish: boolean;
|
||||
showSkipAndFinish: boolean; // Used for quick create actions
|
||||
disableFinish?: boolean;
|
||||
@ -36,6 +39,6 @@ export interface WizardNavigationConfig {
|
||||
* Event interface for wizard navigation actions
|
||||
*/
|
||||
export interface WizardNavigationEvent {
|
||||
action: 'next' | 'previous' | 'cancel' | 'finish';
|
||||
action: 'next' | 'previous' | 'cancel' | 'finish' | 'back';
|
||||
currentStepIndex?: number;
|
||||
}
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
<div class="room-basic-creation-page fade-in">
|
||||
<!-- Header Section -->
|
||||
<header class="page-header">
|
||||
<mat-icon class="ov-room-icon page-icon">video_chat</mat-icon>
|
||||
<div class="page-title-group">
|
||||
<h2 class="page-title">Create Room</h2>
|
||||
<p class="page-description">
|
||||
Create a new video room with optional name prefix. For advanced options, use the room wizard.
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Form Section -->
|
||||
<main class="page-content">
|
||||
<form [formGroup]="roomCreationForm" class="room-basic-creation-form" (ngSubmit)="onCreateRoom()">
|
||||
<!-- Room Prefix Field -->
|
||||
<mat-form-field appearance="outline" class="form-field">
|
||||
<mat-label>Room Name Prefix</mat-label>
|
||||
<input matInput formControlName="roomIdPrefix" placeholder="e.g. demo-room" />
|
||||
<mat-icon matSuffix class="ov-settings-icon">label</mat-icon>
|
||||
<mat-hint>Optional prefix for room names. Leave empty for default naming.</mat-hint>
|
||||
@if (roomCreationForm.get('roomIdPrefix')?.hasError('maxlength')) {
|
||||
<mat-error> Room name prefix cannot exceed 50 characters </mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
mat-button
|
||||
color="primary"
|
||||
type="submit"
|
||||
[disabled]="!isFormValid"
|
||||
class="create-button"
|
||||
>
|
||||
<mat-icon>add</mat-icon>
|
||||
<span>Create Room</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
mat-stroked-button
|
||||
type="button"
|
||||
(click)="onOpenAdvancedMode()"
|
||||
class="advanced-button"
|
||||
matTooltip="Open room wizard for advanced configuration"
|
||||
>
|
||||
<mat-icon>handyman</mat-icon>
|
||||
<span>Advanced Setup</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
</div>
|
||||
@ -0,0 +1,174 @@
|
||||
@import '../../../../../../../../src/assets/styles/design-tokens';
|
||||
|
||||
.room-basic-creation-page {
|
||||
@include ov-page-content;
|
||||
@include ov-container;
|
||||
|
||||
padding: var(--ov-meet-spacing-xl);
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--ov-meet-spacing-md);
|
||||
margin-bottom: var(--ov-meet-spacing-xl);
|
||||
|
||||
.page-icon {
|
||||
@include ov-icon(xxl);
|
||||
color: var(--ov-meet-icon-rooms);
|
||||
margin-top: var(--ov-meet-spacing-xs);
|
||||
}
|
||||
|
||||
.page-title-group {
|
||||
flex: 1;
|
||||
|
||||
.page-title {
|
||||
margin: 0 0 var(--ov-meet-spacing-sm) 0;
|
||||
font-size: var(--ov-meet-font-size-xxl);
|
||||
font-weight: var(--ov-meet-font-weight-medium);
|
||||
color: var(--ov-meet-text-primary);
|
||||
line-height: var(--ov-meet-line-height-tight);
|
||||
}
|
||||
|
||||
.page-description {
|
||||
margin: 0;
|
||||
font-size: var(--ov-meet-font-size-lg);
|
||||
color: var(--ov-meet-text-secondary);
|
||||
line-height: var(--ov-meet-line-height-normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-content {
|
||||
.room-basic-creation-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--ov-meet-spacing-xl);
|
||||
|
||||
.form-field {
|
||||
width: 100%;
|
||||
|
||||
// Material form field customization using existing system
|
||||
::ng-deep {
|
||||
.mat-mdc-form-field-outline {
|
||||
border-radius: var(--ov-meet-radius-sm);
|
||||
}
|
||||
|
||||
.mat-mdc-form-field-label {
|
||||
color: var(--ov-meet-text-secondary);
|
||||
}
|
||||
|
||||
.mat-mdc-form-field-hint {
|
||||
color: var(--ov-meet-text-hint);
|
||||
font-size: var(--ov-meet-font-size-xs);
|
||||
}
|
||||
|
||||
// Icon styling in form fields
|
||||
.mat-mdc-form-field-icon-suffix {
|
||||
mat-icon {
|
||||
@include ov-icon(sm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: var(--ov-meet-spacing-md);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: var(--ov-meet-spacing-lg);
|
||||
|
||||
.create-button {
|
||||
@include ov-button-base;
|
||||
flex: 1;
|
||||
max-width: 200px;
|
||||
background-color: var(--ov-meet-color-success);
|
||||
color: var(--ov-meet-text-on-primary);
|
||||
box-shadow: var(--ov-meet-shadow-sm);
|
||||
|
||||
mat-icon {
|
||||
@include ov-icon(sm);
|
||||
margin-right: var(--ov-meet-spacing-xs);
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: var(--ov-meet-font-size-md);
|
||||
font-weight: var(--ov-meet-font-weight-medium);
|
||||
}
|
||||
}
|
||||
|
||||
.advanced-button {
|
||||
@include ov-button-base;
|
||||
flex: 1;
|
||||
max-width: 200px;
|
||||
|
||||
mat-icon {
|
||||
@include ov-icon(sm);
|
||||
margin-right: var(--ov-meet-spacing-xs);
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: var(--ov-meet-font-size-md);
|
||||
font-weight: var(--ov-meet-font-weight-normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include ov-mobile-down {
|
||||
padding: var(--ov-meet-spacing-lg);
|
||||
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: var(--ov-meet-spacing-sm);
|
||||
|
||||
.page-icon {
|
||||
align-self: center;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.page-title-group {
|
||||
.page-title {
|
||||
font-size: var(--ov-meet-font-size-xl);
|
||||
}
|
||||
|
||||
.page-description {
|
||||
font-size: var(--ov-meet-font-size-md);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-content {
|
||||
.room-basic-creation-form {
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
|
||||
.create-button,
|
||||
.advanced-button {
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include ov-tablet-down {
|
||||
.page-content {
|
||||
.room-basic-creation-form {
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
|
||||
.create-button,
|
||||
.advanced-button {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
import { Component, OnDestroy, Output, EventEmitter } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'ov-room-basic-creation',
|
||||
standalone: true,
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatFormFieldModule,
|
||||
MatTooltipModule
|
||||
],
|
||||
templateUrl: './room-basic-creation.component.html',
|
||||
styleUrl: './room-basic-creation.component.scss'
|
||||
})
|
||||
export class RoomBasicCreationComponent implements OnDestroy {
|
||||
@Output() createRoom = new EventEmitter<string | undefined>();
|
||||
@Output() openAdvancedMode = new EventEmitter<void>();
|
||||
|
||||
roomCreationForm: FormGroup;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(private fb: FormBuilder) {
|
||||
this.roomCreationForm = this.fb.group({
|
||||
roomIdPrefix: ['', [Validators.maxLength(50)]]
|
||||
});
|
||||
|
||||
this.roomCreationForm.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
|
||||
// Optional: Save form data to local storage or service if needed
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
onCreateRoom() {
|
||||
if (this.roomCreationForm.valid) {
|
||||
const formValue = this.roomCreationForm.value;
|
||||
this.createRoom.emit(formValue.roomIdPrefix || undefined);
|
||||
}
|
||||
}
|
||||
|
||||
onOpenAdvancedMode() {
|
||||
this.openAdvancedMode.emit();
|
||||
}
|
||||
|
||||
get isFormValid(): boolean {
|
||||
return this.roomCreationForm.valid && !this.roomCreationForm.pending;
|
||||
}
|
||||
}
|
||||
@ -6,14 +6,16 @@
|
||||
editMode ? 'Edit your room settings' : 'Create and configure your video room in a few simple steps'
|
||||
}}
|
||||
</p>
|
||||
<ov-step-indicator
|
||||
[steps]="steps()"
|
||||
[currentStepIndex]="currentStepIndex()"
|
||||
[allowNavigation]="true"
|
||||
[editMode]="editMode"
|
||||
(stepClick)="onStepClick($event)"
|
||||
>
|
||||
</ov-step-indicator>
|
||||
@if (!isBasicCreation()) {
|
||||
<ov-step-indicator
|
||||
[steps]="steps()"
|
||||
[currentStepIndex]="currentStepIndex()"
|
||||
[allowNavigation]="true"
|
||||
[editMode]="editMode"
|
||||
(stepClick)="onStepClick($event)"
|
||||
>
|
||||
</ov-step-indicator>
|
||||
}
|
||||
</header>
|
||||
|
||||
<main class="wizard-content">
|
||||
@ -41,21 +43,30 @@
|
||||
</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>
|
||||
@if (isBasicCreation()) {
|
||||
<!-- Basic Room Creation -->
|
||||
<ov-room-basic-creation
|
||||
(createRoom)="createRoom($event)"
|
||||
(openAdvancedMode)="onOpenAdvancedMode()"
|
||||
></ov-room-basic-creation>
|
||||
} @else {
|
||||
<!-- Room Wizard Steps -->
|
||||
@switch (currentStep()?.id) {
|
||||
@case ('roomDetails') {
|
||||
<ov-room-wizard-room-details></ov-room-wizard-room-details>
|
||||
}
|
||||
@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>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,15 +74,29 @@
|
||||
</main>
|
||||
|
||||
<footer class="wizard-footer">
|
||||
<ov-wizard-nav
|
||||
[config]="navigationConfig()"
|
||||
[currentStepId]="currentStepIndex()"
|
||||
(previous)="onPrevious()"
|
||||
(next)="onNext()"
|
||||
(cancel)="onCancel()"
|
||||
(finish)="onFinish()"
|
||||
>
|
||||
</ov-wizard-nav>
|
||||
@if (isBasicCreation()) {
|
||||
<div class="basic-nav">
|
||||
<nav class="wizard-navigation">
|
||||
<button mat-stroked-button class="cancel-button" (click)="onCancel()">
|
||||
<mat-icon>close</mat-icon>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
} @else {
|
||||
<ov-wizard-nav
|
||||
class="wizard-nav"
|
||||
[config]="navigationConfig()"
|
||||
[currentStepId]="currentStepIndex()"
|
||||
[backButtonText]="'Back to simple mode'"
|
||||
(previous)="onPrevious()"
|
||||
(next)="onNext()"
|
||||
(cancel)="onCancel()"
|
||||
(back)="onBack()"
|
||||
(finish)="onFinish()"
|
||||
>
|
||||
</ov-wizard-nav>
|
||||
}
|
||||
</footer>
|
||||
}
|
||||
</div>
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
@include ov-page-content;
|
||||
min-height: 600px;
|
||||
gap: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.wizard-header {
|
||||
@ -37,7 +38,30 @@
|
||||
}
|
||||
|
||||
.wizard-footer {
|
||||
margin-top: auto;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
.basic-nav,
|
||||
.wizard-nav {
|
||||
width: 100%;
|
||||
max-width: 650px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.basic-nav .wizard-navigation {
|
||||
padding: var(--ov-meet-spacing-md) 0 0 0;
|
||||
}
|
||||
.cancel-button {
|
||||
@include ov-button-base;
|
||||
border-radius: var(--ov-meet-radius-sm);
|
||||
min-width: 120px;
|
||||
color: var(--ov-meet-text-secondary);
|
||||
border-color: var(--ov-meet-border-color-strong);
|
||||
&:hover {
|
||||
background-color: var(--ov-meet-surface-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-label {
|
||||
|
||||
@ -9,11 +9,12 @@ import { 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';
|
||||
import { RoomWizardBasicInfoComponent } from './steps/basic-info/basic-info.component';
|
||||
import { RoomWizardRoomDetailsComponent } from './steps/room-details/room-details.component';
|
||||
import { RecordingLayoutComponent } from './steps/recording-layout/recording-layout.component';
|
||||
import { RecordingPreferencesComponent } from './steps/recording-preferences/recording-preferences.component';
|
||||
import { RecordingTriggerComponent } from './steps/recording-trigger/recording-trigger.component';
|
||||
import { RoomPreferencesComponent } from './steps/room-preferences/room-preferences.component';
|
||||
import { RoomBasicCreationComponent } from '../room-basic-creation/room-basic-creation.component';
|
||||
|
||||
@Component({
|
||||
selector: 'ov-room-wizard',
|
||||
@ -26,7 +27,8 @@ import { RoomPreferencesComponent } from './steps/room-preferences/room-preferen
|
||||
MatIconModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatSlideToggleModule,
|
||||
RoomWizardBasicInfoComponent,
|
||||
RoomBasicCreationComponent,
|
||||
RoomWizardRoomDetailsComponent,
|
||||
RecordingPreferencesComponent,
|
||||
RecordingTriggerComponent,
|
||||
RecordingLayoutComponent,
|
||||
@ -38,9 +40,9 @@ import { RoomPreferencesComponent } from './steps/room-preferences/room-preferen
|
||||
export class RoomWizardComponent implements OnInit {
|
||||
editMode: boolean = false;
|
||||
roomId?: string;
|
||||
existingRoomData?: MeetRoomOptions;
|
||||
existingRoomData?: MeetRoomOptions; // Edit mode
|
||||
isCreatingRoom = signal(false);
|
||||
|
||||
isBasicCreation = signal(true);
|
||||
steps: Signal<WizardStep[]>;
|
||||
currentStep: Signal<WizardStep | undefined>;
|
||||
currentStepIndex: Signal<number>;
|
||||
@ -89,6 +91,9 @@ export class RoomWizardComponent implements OnInit {
|
||||
try {
|
||||
const { roomIdPrefix, autoDeletionDate, preferences } = await this.roomService.getRoom(this.roomId);
|
||||
this.existingRoomData = { roomIdPrefix, autoDeletionDate, preferences };
|
||||
if (this.existingRoomData) {
|
||||
this.isBasicCreation.set(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading room data:', error);
|
||||
// Navigate back to rooms list if room not found
|
||||
@ -96,10 +101,19 @@ export class RoomWizardComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
onOpenAdvancedMode() {
|
||||
this.isBasicCreation.set(false);
|
||||
this.wizardService.goToStep(0); // Reset to first step
|
||||
}
|
||||
|
||||
onPrevious() {
|
||||
this.wizardService.goToPreviousStep();
|
||||
}
|
||||
|
||||
onBack() {
|
||||
this.isBasicCreation.set(true);
|
||||
}
|
||||
|
||||
onNext() {
|
||||
this.wizardService.goToNextStep();
|
||||
}
|
||||
@ -113,6 +127,22 @@ export class RoomWizardComponent implements OnInit {
|
||||
await this.navigationService.navigateTo('rooms', undefined, true);
|
||||
}
|
||||
|
||||
async createRoom(roomIdPrefix?: string) {
|
||||
try {
|
||||
// Call the room service to create a new room
|
||||
const { moderatorRoomUrl } = await this.roomService.createRoom({ roomIdPrefix });
|
||||
await this.navigationService.redirectTo(moderatorRoomUrl);
|
||||
} catch (error) {
|
||||
const errorMessage = `Failed to create room ${roomIdPrefix}`;
|
||||
this.notificationService.showSnackbar(errorMessage);
|
||||
console.error(errorMessage, error);
|
||||
} finally {
|
||||
this.wizardService.resetWizard();
|
||||
// Deactivate loading state
|
||||
this.isCreatingRoom.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
async onFinish() {
|
||||
const roomOptions = this.wizardService.roomOptions();
|
||||
console.log('Wizard completed with data:', roomOptions);
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
<div class="basic-info-step fade-in">
|
||||
<div class="room-details-step fade-in">
|
||||
<!-- Header Section -->
|
||||
<header class="step-header">
|
||||
<mat-icon class="ov-room-icon step-icon">video_chat</mat-icon>
|
||||
<div class="step-title-group">
|
||||
<h3 class="step-title">Basic Information</h3>
|
||||
<h3 class="step-title">Room Details</h3>
|
||||
<p class="step-description">
|
||||
Configure your room's basic settings including name prefix and automatic deletion date
|
||||
Configure your room's details including name prefix and automatic deletion date
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Form Section -->
|
||||
<main class="step-content">
|
||||
<form [formGroup]="basicInfoForm" class="basic-info-form">
|
||||
<form [formGroup]="roomDetailsForm" class="room-details-form">
|
||||
<!-- Room Prefix Field -->
|
||||
<mat-form-field appearance="outline" class="form-field">
|
||||
<mat-label>Room Name Prefix</mat-label>
|
||||
<input matInput formControlName="roomIdPrefix" placeholder="e.g. demo-room" />
|
||||
<mat-icon matSuffix class="ov-settings-icon">label</mat-icon>
|
||||
<mat-hint>Optional prefix for room names. Leave empty for default naming.</mat-hint>
|
||||
@if (basicInfoForm.get('roomIdPrefix')?.hasError('maxlength')) {
|
||||
@if (roomDetailsForm.get('roomIdPrefix')?.hasError('maxlength')) {
|
||||
<mat-error> Room name prefix cannot exceed 50 characters </mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
@ -42,7 +42,7 @@
|
||||
matSuffix
|
||||
mat-icon-button
|
||||
type="button"
|
||||
[disabled]="basicInfoForm.get('autoDeletionDate')?.disabled"
|
||||
[disabled]="roomDetailsForm.get('autoDeletionDate')?.disabled"
|
||||
(click)="clearDeletionDate()"
|
||||
matTooltip="Clear date selection"
|
||||
class="clear-date-button"
|
||||
@ -60,7 +60,7 @@
|
||||
</mat-form-field>
|
||||
|
||||
<!-- Time Selection (only shown when date is selected) -->
|
||||
@if (basicInfoForm.get('autoDeletionDate')?.value) {
|
||||
@if (roomDetailsForm.get('autoDeletionDate')?.value) {
|
||||
<div class="time-selection-container">
|
||||
<div class="time-selection-row">
|
||||
<mat-form-field appearance="outline" class="time-field">
|
||||
@ -89,7 +89,7 @@
|
||||
<mat-icon matSuffix class="ov-settings-icon">access_time</mat-icon>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@if (!basicInfoForm.hasError('minFutureDateTime')) {
|
||||
@if (!roomDetailsForm.hasError('minFutureDateTime')) {
|
||||
<div class="time-hint">
|
||||
<mat-icon class="hint-icon material-symbols-outlined material-icons">auto_delete</mat-icon>
|
||||
<span>Room will be deleted at {{ getFormattedDateTime() }}</span>
|
||||
@ -1,6 +1,6 @@
|
||||
@import '../../../../../../../../../../src/assets/styles/design-tokens';
|
||||
|
||||
.basic-info-step {
|
||||
.room-details-step {
|
||||
@include ov-page-content;
|
||||
@include ov-container;
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
.step-content {
|
||||
// margin-bottom: var(--ov-meet-spacing-xl);
|
||||
|
||||
.basic-info-form {
|
||||
.room-details-form {
|
||||
@include ov-grid-responsive(280px);
|
||||
gap: var(--ov-meet-spacing-lg);
|
||||
|
||||
@ -144,7 +144,7 @@
|
||||
}
|
||||
|
||||
.step-content {
|
||||
.basic-info-form {
|
||||
.room-details-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--ov-meet-spacing-md);
|
||||
@ -170,7 +170,7 @@
|
||||
|
||||
@include ov-tablet-down {
|
||||
.step-content {
|
||||
.basic-info-form {
|
||||
.room-details-form {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@ -1,18 +1,18 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { RoomWizardBasicInfoComponent } from './basic-info.component';
|
||||
import { RoomWizardRoomDetailsComponent } from './room-details.component';
|
||||
|
||||
describe('BasicInfoComponent', () => {
|
||||
let component: RoomWizardBasicInfoComponent;
|
||||
let fixture: ComponentFixture<RoomWizardBasicInfoComponent>;
|
||||
let component: RoomWizardRoomDetailsComponent;
|
||||
let fixture: ComponentFixture<RoomWizardRoomDetailsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [RoomWizardBasicInfoComponent]
|
||||
imports: [RoomWizardRoomDetailsComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(RoomWizardBasicInfoComponent);
|
||||
fixture = TestBed.createComponent(RoomWizardRoomDetailsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
@ -13,7 +13,7 @@ import { MeetRoomOptions } from '@lib/typings/ce';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'ov-room-wizard-basic-info',
|
||||
selector: 'ov-room-wizard-room-details',
|
||||
standalone: true,
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
@ -26,11 +26,11 @@ import { Subject, takeUntil } from 'rxjs';
|
||||
MatSelectModule,
|
||||
MatTooltipModule
|
||||
],
|
||||
templateUrl: './basic-info.component.html',
|
||||
styleUrl: './basic-info.component.scss'
|
||||
templateUrl: './room-details.component.html',
|
||||
styleUrl: './room-details.component.scss'
|
||||
})
|
||||
export class RoomWizardBasicInfoComponent implements OnDestroy {
|
||||
basicInfoForm: FormGroup;
|
||||
export class RoomWizardRoomDetailsComponent implements OnDestroy {
|
||||
roomDetailsForm: FormGroup;
|
||||
|
||||
// Arrays for time selection
|
||||
hours = Array.from({ length: 24 }, (_, i) => ({ value: i, display: i.toString().padStart(2, '0') }));
|
||||
@ -40,9 +40,9 @@ export class RoomWizardBasicInfoComponent implements OnDestroy {
|
||||
|
||||
constructor(private wizardService: RoomWizardStateService) {
|
||||
const currentStep = this.wizardService.currentStep();
|
||||
this.basicInfoForm = currentStep!.formGroup;
|
||||
this.roomDetailsForm = currentStep!.formGroup;
|
||||
|
||||
this.basicInfoForm.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
|
||||
this.roomDetailsForm.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
|
||||
this.saveFormData(value);
|
||||
});
|
||||
}
|
||||
@ -71,7 +71,7 @@ export class RoomWizardBasicInfoComponent implements OnDestroy {
|
||||
};
|
||||
|
||||
// Always save to wizard state (including when values are cleared)
|
||||
this.wizardService.updateStepData('basic', stepData);
|
||||
this.wizardService.updateStepData('roomDetails', stepData);
|
||||
}
|
||||
|
||||
get minDate(): Date {
|
||||
@ -81,11 +81,11 @@ export class RoomWizardBasicInfoComponent implements OnDestroy {
|
||||
}
|
||||
|
||||
get hasDateSelected(): boolean {
|
||||
return !!this.basicInfoForm.get('autoDeletionDate')?.value;
|
||||
return !!this.roomDetailsForm.get('autoDeletionDate')?.value;
|
||||
}
|
||||
|
||||
getFormattedDateTime(): string {
|
||||
const formValue = this.basicInfoForm.value;
|
||||
const formValue = this.roomDetailsForm.value;
|
||||
if (!formValue.autoDeletionDate) {
|
||||
return '';
|
||||
}
|
||||
@ -109,7 +109,7 @@ export class RoomWizardBasicInfoComponent implements OnDestroy {
|
||||
}
|
||||
|
||||
clearDeletionDate() {
|
||||
this.basicInfoForm.patchValue({
|
||||
this.roomDetailsForm.patchValue({
|
||||
autoDeletionDate: null,
|
||||
autoDeletionHour: 23,
|
||||
autoDeletionMinute: 59
|
||||
@ -1,219 +0,0 @@
|
||||
// @import '../../../../../../../src/assets/styles/design-tokens';
|
||||
|
||||
// Use page loading utility
|
||||
// .loading-container {
|
||||
// @extend .ov-page-loading;
|
||||
|
||||
// .loading-content .loading-header .loading-title .loading-icon {
|
||||
// color: var(--ov-meet-icon-rooms);
|
||||
// }
|
||||
// }
|
||||
|
||||
// Use table page actions utility
|
||||
// .rooms-actions {
|
||||
// @extend .ov-table-page-actions;
|
||||
|
||||
// .create-room-btn {
|
||||
// @include ov-button-base;
|
||||
|
||||
// mat-icon {
|
||||
// @include ov-icon(md);
|
||||
// margin-right: var(--ov-meet-spacing-sm);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// Use search field utility
|
||||
// .search-bar {
|
||||
// .search-field {
|
||||
// @extend .ov-search-field;
|
||||
// min-width: 400px;
|
||||
// max-width: 500px;
|
||||
// }
|
||||
// }
|
||||
|
||||
// Use table page container utility
|
||||
// .rooms-table-container {
|
||||
// @extend .ov-table-page-container;
|
||||
// }
|
||||
|
||||
// Responsive table utilities
|
||||
// .table-wrapper {
|
||||
// &.desktop-view {
|
||||
// display: block;
|
||||
|
||||
// @include ov-tablet-down {
|
||||
// display: none;
|
||||
// }
|
||||
// }
|
||||
|
||||
// &.mobile-view {
|
||||
// display: none;
|
||||
|
||||
// @include ov-tablet-down {
|
||||
// display: block;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Use data table utility
|
||||
// .rooms-table {
|
||||
// @extend .ov-data-table;
|
||||
|
||||
// .mat-mdc-header-cell {
|
||||
// &.room-header {
|
||||
// @extend .primary-header;
|
||||
// }
|
||||
|
||||
// &.actions-header {
|
||||
// @extend .actions-header;
|
||||
// }
|
||||
// }
|
||||
|
||||
// .mat-mdc-cell {
|
||||
// &.room-cell {
|
||||
// @extend .primary-cell;
|
||||
// }
|
||||
|
||||
// &.actions-cell {
|
||||
// @extend .actions-cell;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// Use mobile card utilities
|
||||
// .mobile-rooms-list {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// gap: var(--ov-meet-spacing-md);
|
||||
|
||||
// .room-mobile-card {
|
||||
// @include ov-card;
|
||||
// @include ov-theme-transition;
|
||||
|
||||
// &:hover {
|
||||
// @include ov-hover-lift(-2px);
|
||||
// }
|
||||
|
||||
// .room-card-header {
|
||||
// display: flex;
|
||||
// justify-content: space-between;
|
||||
// align-items: flex-start;
|
||||
// margin-bottom: var(--ov-meet-spacing-sm);
|
||||
|
||||
// .room-title {
|
||||
// @extend .primary-text;
|
||||
// }
|
||||
|
||||
// .room-status {
|
||||
// @extend .ov-status-badge;
|
||||
// }
|
||||
// }
|
||||
|
||||
// .room-card-content {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// gap: var(--ov-meet-spacing-xs);
|
||||
|
||||
// .room-detail {
|
||||
// display: flex;
|
||||
// justify-content: space-between;
|
||||
// font-size: var(--ov-meet-font-size-sm);
|
||||
|
||||
// .detail-label {
|
||||
// color: var(--ov-meet-text-secondary);
|
||||
// }
|
||||
|
||||
// .detail-value {
|
||||
// color: var(--ov-meet-text-primary);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// .room-card-actions {
|
||||
// @extend .ov-action-buttons;
|
||||
// margin-top: var(--ov-meet-spacing-md);
|
||||
// padding-top: var(--ov-meet-spacing-md);
|
||||
// border-top: 1px solid var(--ov-meet-border-color-light);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Use info display and status utilities
|
||||
// .room-info {
|
||||
// @extend .ov-info-display;
|
||||
// }
|
||||
|
||||
// .status-badge {
|
||||
// @extend .ov-status-badge;
|
||||
// }
|
||||
|
||||
// .participant-count {
|
||||
// @extend .ov-date-info;
|
||||
// }
|
||||
|
||||
// .creation-date {
|
||||
// @extend .ov-date-info;
|
||||
// }
|
||||
|
||||
// // Use action buttons utility
|
||||
// .action-buttons {
|
||||
// @extend .ov-action-buttons;
|
||||
|
||||
// .mat-mdc-icon-button {
|
||||
// &.primary-action {
|
||||
// color: var(--ov-meet-color-primary);
|
||||
// }
|
||||
|
||||
// &.room-preferences-btn {
|
||||
// color: var(--ov-meet-icon-settings);
|
||||
// }
|
||||
|
||||
// &.copy-link-btn {
|
||||
// color: var(--ov-meet-text-secondary);
|
||||
// }
|
||||
|
||||
// &.view-recordings-btn {
|
||||
// color: var(--ov-meet-icon-recordings);
|
||||
// }
|
||||
|
||||
// &.delete-room-btn {
|
||||
// color: var(--ov-meet-color-error);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Use empty state utility
|
||||
// .no-rooms-state {
|
||||
// @extend .ov-empty-state;
|
||||
|
||||
// .empty-icon {
|
||||
// @include ov-icon(xl);
|
||||
// color: var(--ov-meet-text-hint);
|
||||
// margin-bottom: var(--ov-meet-spacing-lg);
|
||||
// display: block;
|
||||
// }
|
||||
|
||||
// .getting-started-actions {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// gap: var(--ov-meet-spacing-md);
|
||||
// align-items: center;
|
||||
|
||||
// button {
|
||||
// @include ov-button-base;
|
||||
|
||||
// mat-icon {
|
||||
// @include ov-icon(md);
|
||||
// margin-right: var(--ov-meet-spacing-sm);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Use focus utilities
|
||||
// .mat-mdc-checkbox,
|
||||
// .mat-mdc-icon-button,
|
||||
// .mat-mdc-button {
|
||||
// @extend .ov-focus-visible;
|
||||
// }
|
||||
@ -65,10 +65,10 @@ export class RoomWizardStateService {
|
||||
// Define wizard steps
|
||||
const baseSteps: WizardStep[] = [
|
||||
{
|
||||
id: 'basic',
|
||||
id: 'roomDetails',
|
||||
label: 'Room Details',
|
||||
isCompleted: editMode, // In edit mode, mark as completed but not editable
|
||||
isActive: !editMode, // Start with basic step active in create mode
|
||||
isActive: !editMode, // Start with roomDetails step active in create mode
|
||||
isVisible: true,
|
||||
formGroup: this.formBuilder.group(
|
||||
{
|
||||
@ -174,7 +174,7 @@ export class RoomWizardStateService {
|
||||
];
|
||||
|
||||
this._steps.set(baseSteps);
|
||||
const initialStepIndex = editMode ? 1 : 0; // Skip basic step in edit mode
|
||||
const initialStepIndex = editMode ? 1 : 0; // Skip roomDetails step in edit mode
|
||||
this._currentStepIndex.set(initialStepIndex);
|
||||
|
||||
// Update step visibility after index is set
|
||||
@ -192,7 +192,7 @@ export class RoomWizardStateService {
|
||||
let updatedOptions: MeetRoomOptions;
|
||||
|
||||
switch (stepId) {
|
||||
case 'basic':
|
||||
case 'roomDetails':
|
||||
updatedOptions = {
|
||||
...currentOptions
|
||||
};
|
||||
@ -360,9 +360,10 @@ export class RoomWizardStateService {
|
||||
return {
|
||||
showPrevious: !isFirstStep,
|
||||
showNext: !isLastStep,
|
||||
showCancel: true,
|
||||
showCancel: false,
|
||||
showBack: true,
|
||||
showFinish: isLastStep,
|
||||
showSkipAndFinish: !isEditMode && isFirstStep,
|
||||
showSkipAndFinish: false, // Skip and finish is not used in this wizard
|
||||
disableFinish: isSomeStepInvalid,
|
||||
nextLabel: 'Next',
|
||||
previousLabel: 'Previous',
|
||||
@ -373,13 +374,13 @@ export class RoomWizardStateService {
|
||||
|
||||
/**
|
||||
* Checks if the wizard is in edit mode.
|
||||
* Edit mode is determined by whether the basic step is completed and its form is disabled.
|
||||
* Edit mode is determined by whether the roomDetails step is completed and its form is disabled.
|
||||
* @returns True if in edit mode, false otherwise
|
||||
*/
|
||||
private isEditMode(): boolean {
|
||||
const visibleSteps = this._visibleSteps();
|
||||
const basicStep = visibleSteps.find((step) => step.id === 'basic');
|
||||
const isEditMode = !!basicStep && basicStep.isCompleted && basicStep.formGroup.disabled;
|
||||
const roomDetailsStep = visibleSteps.find((step) => step.id === 'roomDetails');
|
||||
const isEditMode = !!roomDetailsStep && roomDetailsStep.isCompleted && roomDetailsStep.formGroup.disabled;
|
||||
return isEditMode;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user