frontend: Revamp video room access UI with new design and navigation features

This commit is contained in:
Carlos Santos 2025-07-02 18:27:58 +02:00
parent a3560ee845
commit 0cf5eba604
5 changed files with 366 additions and 111 deletions

View File

@ -1,13 +1,31 @@
@if (!showRoom) { @if (!showRoom) {
<div class="form-container"> <div class="ov-page-container">
<div class="card-wrapper"> <div class="room-access-container fade-in">
<mat-card class="form-card"> <!-- Header Section -->
<h2 class="form-title"> <div class="room-header">
Access room <strong>{{ roomId }}</strong> <mat-icon class="ov-room-icon room-icon">video_chat</mat-icon>
</h2> <div class="room-info">
<form [formGroup]="participantForm" (ngSubmit)="submitAccessRoom()"> <h1 class="room-title">{{ roomId }}</h1>
<mat-form-field appearance="outline" class="full-width"> <p class="room-subtitle">Choose how you want to proceed</p>
<mat-label>Name</mat-label> </div>
</div>
<!-- Action Cards Grid -->
<div class="action-cards-grid">
<!-- Join Room Card -->
<mat-card class="action-card primary-card fade-in">
<mat-card-header class="card-header">
<mat-icon class="ov-room-icon card-icon">meeting_room</mat-icon>
<div class="card-title-group">
<mat-card-title>Join Meeting</mat-card-title>
<mat-card-subtitle>Enter the room and start connecting</mat-card-subtitle>
</div>
</mat-card-header>
<mat-card-content class="card-content">
<form [formGroup]="participantForm" (ngSubmit)="submitAccessRoom()" class="join-form">
<mat-form-field appearance="outline" class="name-field">
<mat-label>Your display name</mat-label>
<input <input
id="participant-name-input" id="participant-name-input"
matInput matInput
@ -15,6 +33,7 @@
formControlName="name" formControlName="name"
required required
/> />
<mat-icon matSuffix class="ov-action-icon">person</mat-icon>
@if (participantForm.get('name')?.hasError('minlength')) { @if (participantForm.get('name')?.hasError('minlength')) {
<mat-error> The name must be at least <strong>4 characters</strong> </mat-error> <mat-error> The name must be at least <strong>4 characters</strong> </mat-error>
} }
@ -32,22 +51,56 @@
mat-raised-button mat-raised-button
color="primary" color="primary"
id="participant-name-submit" id="participant-name-submit"
class="full-width" type="submit"
class="join-button"
[disabled]="participantForm.invalid" [disabled]="participantForm.invalid"
> >
Continue to room <span>Join Meeting</span>
</button> </button>
</form> </form>
</mat-card-content>
</mat-card> </mat-card>
<mat-card class="recordings-card"> <!-- View Recordings Card -->
<div class="recordings-content"> @if (showRecordingCard) {
<h3>View recordings</h3> <mat-card class="action-card secondary-card fade-in-delayed">
<button id="view-recordings-btn" mat-stroked-button color="accent" (click)="goToRecordings()"> <mat-card-header class="card-header">
Go to recordings <mat-icon class="ov-recording-icon card-icon">video_library</mat-icon>
<div class="card-title-group">
<mat-card-title>View Recordings</mat-card-title>
<mat-card-subtitle>Browse and manage past recordings</mat-card-subtitle>
</div>
</mat-card-header>
<mat-card-content class="card-content">
<div class="recordings-info">
<p class="recordings-description">
Access previously recorded meetings from this room. You can watch, download, or
manage existing recordings.
</p>
</div>
<button
id="view-recordings-btn"
mat-stroked-button
color="accent"
(click)="goToRecordings()"
class="recordings-button"
>
<span>Browse Recordings</span>
</button>
</mat-card-content>
</mat-card>
}
</div>
<!-- Quick Actions -->
<div class="quick-actions fade-in-delayed-more">
<button mat-button class="quick-action-button" (click)="goBack()">
<mat-icon>arrow_back</mat-icon>
<span>Back to Rooms</span>
</button> </button>
</div> </div>
</mat-card>
</div> </div>
</div> </div>
} @else { } @else {

View File

@ -1,81 +1,264 @@
.form-container { @import '../../../../../../src/assets/styles/design-tokens';
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: var(--ov-meet-surface-background);
padding: 20px;
.card-wrapper { // Room Access Container - Main layout using design tokens
display: flex; .room-access-container {
gap: 40px; @include ov-container;
flex-wrap: wrap; @include ov-page-content;
justify-content: center; min-height: 100vh;
align-items: flex-start; padding-top: var(--ov-meet-spacing-xxl);
background: var(--ov-meet-background-color);
gap: 0;
} }
.form-card { // Room Header - Clean title section
width: 400px; .room-header {
padding: 20px; @include ov-flex-center;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); flex-direction: column;
border-radius: var(--ov-meet-surface-radius); gap: var(--ov-meet-spacing-md);
background-color: var(--ov-meet-surface-primary); margin-bottom: var(--ov-meet-spacing-xxl);
text-align: center;
.room-icon {
@include ov-icon(xl);
color: var(--ov-meet-icon-rooms);
margin-bottom: var(--ov-meet-spacing-sm);
}
.room-info {
.room-title {
margin: 0;
font-size: var(--ov-meet-font-size-hero);
font-weight: var(--ov-meet-font-weight-light);
color: var(--ov-meet-text-primary);
line-height: var(--ov-meet-line-height-tight);
}
.room-subtitle {
margin: var(--ov-meet-spacing-xs) 0 0 0;
font-size: var(--ov-meet-font-size-lg);
color: var(--ov-meet-text-secondary);
line-height: var(--ov-meet-line-height-normal);
}
}
}
// Action Cards Grid - Responsive layout
.action-cards-grid {
@include ov-grid-responsive(320px);
gap: var(--ov-meet-spacing-xl);
margin-bottom: var(--ov-meet-spacing-xxl);
justify-content: center;
// When there's only one card, limit its width to maintain visual consistency
&:has(.action-card:only-child) {
display: flex;
justify-content: center;
.action-card {
max-width: 400px;
width: 100%;
}
}
@include ov-tablet-down {
grid-template-columns: 1fr;
gap: var(--ov-meet-spacing-lg);
// On tablets and mobile, single cards should use full width
&:has(.action-card:only-child) {
.action-card {
max-width: none;
}
}
}
}
// Action Card Base - Consistent card styling
.action-card {
@include ov-card;
@include ov-hover-lift(-4px);
@include ov-theme-transition;
padding: 0;
overflow: hidden;
min-height: 300px;
display: flex;
flex-direction: column;
// Card Header
.card-header {
padding: var(--ov-meet-spacing-lg);
border-bottom: 1px solid var(--ov-meet-border-color-light);
display: flex;
align-items: center;
gap: var(--ov-meet-spacing-md);
flex-shrink: 0;
.card-icon {
@include ov-icon(lg);
flex-shrink: 0;
}
.card-title-group {
flex: 1;
.mat-mdc-card-title {
margin: 0;
font-size: var(--ov-meet-font-size-xl);
font-weight: var(--ov-meet-font-weight-semibold);
color: var(--ov-meet-text-primary);
line-height: var(--ov-meet-line-height-tight);
}
.mat-mdc-card-subtitle {
margin: var(--ov-meet-spacing-xs) 0 0 0;
font-size: var(--ov-meet-font-size-sm);
color: var(--ov-meet-text-secondary);
line-height: var(--ov-meet-line-height-normal);
}
}
}
// Card Content
.card-content {
padding: var(--ov-meet-spacing-lg);
flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
} }
.recordings-card {
padding: 16px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
border-radius: var(--ov-meet-surface-radius);
background-color: var(--ov-meet-surface-primary);
width: fit-content;
align-self: center;
} }
.recordings-content { // Primary Card - Join meeting styling
.primary-card {
.card-header {
background: linear-gradient(135deg, var(--ov-meet-surface-color) 0%, var(--ov-meet-color-primary-light) 180%);
color: var(--ov-meet-text-on-primary);
}
}
// Secondary Card - Recordings styling
.secondary-card {
.card-header {
background: linear-gradient(135deg, var(--ov-meet-surface-color) 0%, var(--ov-meet-color-accent) 180%);
}
}
// Join Form - Form styling
.join-form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; gap: var(--ov-meet-spacing-lg);
gap: 12px; flex: 1;
}
.form-title { .name-field {
text-align: center;
margin-bottom: 20px;
color: var(--ov-meet-text-primary);
strong {
font-style: italic;
}
}
.full-width {
width: 100%; width: 100%;
margin-bottom: 20px;
.mat-mdc-form-field-icon-suffix {
color: var(--ov-meet-text-hint);
}
} }
button { .join-button {
@include ov-button-base;
height: 56px; height: 56px;
border-radius: var(--ov-meet-surface-radius); display: flex;
} align-items: center;
justify-content: center;
button:not([disabled]) { gap: var(--ov-meet-spacing-sm);
background-color: var(--ov-meet-color-accent); margin-top: auto;
color: var(--ov-meet-text-on-accent); background-color: var(--ov-meet-color-secondary);
color: var(--ov-meet-text-on-secondary);
} }
} }
// Recordings Info - Content for recordings card
.recordings-info {
flex: 1;
display: flex;
flex-direction: column;
gap: var(--ov-meet-spacing-lg);
.recordings-description {
margin: 0;
font-size: var(--ov-meet-font-size-md);
color: var(--ov-meet-text-secondary);
line-height: var(--ov-meet-line-height-relaxed);
}
}
.recordings-button {
@include ov-button-base;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
gap: var(--ov-meet-spacing-sm);
margin-top: auto;
}
// Quick Actions - Footer actions
.quick-actions {
@include ov-flex-center;
margin-top: var(--ov-meet-spacing-xl);
.quick-action-button {
display: flex;
align-items: center;
gap: var(--ov-meet-spacing-sm);
color: var(--ov-meet-text-secondary);
@include ov-theme-transition;
&:hover {
color: var(--ov-meet-text-primary);
background-color: var(--ov-meet-surface-hover);
}
}
}
// Responsive adjustments
@include ov-mobile-down {
.room-access-container {
padding: var(--ov-meet-spacing-lg);
padding-top: var(--ov-meet-spacing-xl);
}
.room-header {
margin-bottom: var(--ov-meet-spacing-xl);
.room-info .room-title {
font-size: var(--ov-meet-font-size-xxl);
}
}
.action-card {
min-height: auto;
.card-header {
padding: var(--ov-meet-spacing-md);
.card-title-group {
.mat-mdc-card-title {
font-size: var(--ov-meet-font-size-lg);
}
}
}
.card-content {
padding: var(--ov-meet-spacing-md);
}
}
}
// Custom leave button styling (existing functionality)
::ng-deep { ::ng-deep {
#media-buttons-container .custom-leave-btn > button { #media-buttons-container .custom-leave-btn > button {
&:hover { &:hover {
background-color: var(--ov-error-color) !important; background-color: var(--ov-meet-color-error) !important;
} }
text-align: center; text-align: center;
background-color: var(--ov-error-color) !important; background-color: var(--ov-meet-color-error) !important;
color: var(--ov-secondary-action-color); color: var(--ov-meet-text-on-primary);
border-radius: var(--ov-leave-button-radius) !important; border-radius: var(--ov-meet-radius-sm) !important;
width: 65px !important; width: 65px !important;
margin: 0px !important; margin: 0px !important;
} }

View File

@ -61,7 +61,7 @@ export class VideoRoomComponent implements OnInit, OnDestroy {
name: new FormControl('', [Validators.required, Validators.minLength(4)]) name: new FormControl('', [Validators.required, Validators.minLength(4)])
}); });
showRoom = false; showRoom = false;
showRecordingCard = false;
roomId = ''; roomId = '';
roomSecret = ''; roomSecret = '';
participantName = ''; participantName = '';
@ -90,6 +90,14 @@ export class VideoRoomComponent implements OnInit, OnDestroy {
this.roomId = this.roomService.getRoomId(); this.roomId = this.roomService.getRoomId();
this.roomSecret = this.roomService.getRoomSecret(); this.roomSecret = this.roomService.getRoomSecret();
const { recordings } = await this.recManagerService.listRecordings({
maxItems: 1,
roomId: this.roomId,
fields: 'recordingId'
});
this.showRecordingCard = recordings.length > 0;
await this.initializeParticipantName(); await this.initializeParticipantName();
} }
@ -224,6 +232,14 @@ export class VideoRoomComponent implements OnInit, OnDestroy {
} }
} }
async goBack() {
try {
await this.navigationService.navigateTo('rooms');
} catch (error) {
console.error('Error navigating back to rooms:', error);
}
}
onParticipantConnected(event: ParticipantModel) { onParticipantConnected(event: ParticipantModel) {
const message: WebComponentOutboundEventMessage = { const message: WebComponentOutboundEventMessage = {
event: WebComponentEvent.JOIN, event: WebComponentEvent.JOIN,

View File

@ -7,6 +7,8 @@
--ov-meet-color-primary: #1976d2; --ov-meet-color-primary: #1976d2;
--ov-meet-color-primary-light: #42a5f5; --ov-meet-color-primary-light: #42a5f5;
--ov-meet-color-primary-dark: #1565c0; --ov-meet-color-primary-dark: #1565c0;
--ov-meet-color-secondary: #585858;
--ov-meet-color-secondary-light: #b0a7a8;
--ov-meet-color-accent: #7b1fa2; --ov-meet-color-accent: #7b1fa2;
--ov-meet-color-accent-light: #9c27b0; --ov-meet-color-accent-light: #9c27b0;
--ov-meet-color-accent-dark: #6a1b9a; --ov-meet-color-accent-dark: #6a1b9a;

View File

@ -17,6 +17,7 @@
--ov-meet-text-hint: #9e9e9e; --ov-meet-text-hint: #9e9e9e;
--ov-meet-text-disabled: #bdbdbd; --ov-meet-text-disabled: #bdbdbd;
--ov-meet-text-on-primary: #ffffff; --ov-meet-text-on-primary: #ffffff;
--ov-meet-text-on-secondary: #ffffff;
--ov-meet-text-on-accent: #ffffff; --ov-meet-text-on-accent: #ffffff;
--ov-meet-text-on-surface: #212121; --ov-meet-text-on-surface: #212121;
--ov-meet-text-on-background: #212121; --ov-meet-text-on-background: #212121;