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,53 +1,106 @@
@if (!showRoom) {
<div class="form-container">
<div class="card-wrapper">
<mat-card class="form-card">
<h2 class="form-title">
Access room <strong>{{ roomId }}</strong>
</h2>
<form [formGroup]="participantForm" (ngSubmit)="submitAccessRoom()">
<mat-form-field appearance="outline" class="full-width">
<mat-label>Name</mat-label>
<input
id="participant-name-input"
matInput
placeholder="Enter your name"
formControlName="name"
required
/>
@if (participantForm.get('name')?.hasError('minlength')) {
<mat-error> The name must be at least <strong>4 characters</strong> </mat-error>
}
@if (participantForm.get('name')?.hasError('required')) {
<mat-error> The name is <strong>required</strong> </mat-error>
}
@if (participantForm.get('name')?.hasError('participantExists')) {
<mat-error>
The name is already taken. <strong> Please choose another name </strong>
</mat-error>
}
</mat-form-field>
<button
mat-raised-button
color="primary"
id="participant-name-submit"
class="full-width"
[disabled]="participantForm.invalid"
>
Continue to room
</button>
</form>
</mat-card>
<mat-card class="recordings-card">
<div class="recordings-content">
<h3>View recordings</h3>
<button id="view-recordings-btn" mat-stroked-button color="accent" (click)="goToRecordings()">
Go to recordings
</button>
<div class="ov-page-container">
<div class="room-access-container fade-in">
<!-- Header Section -->
<div class="room-header">
<mat-icon class="ov-room-icon room-icon">video_chat</mat-icon>
<div class="room-info">
<h1 class="room-title">{{ roomId }}</h1>
<p class="room-subtitle">Choose how you want to proceed</p>
</div>
</mat-card>
</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
id="participant-name-input"
matInput
placeholder="Enter your name"
formControlName="name"
required
/>
<mat-icon matSuffix class="ov-action-icon">person</mat-icon>
@if (participantForm.get('name')?.hasError('minlength')) {
<mat-error> The name must be at least <strong>4 characters</strong> </mat-error>
}
@if (participantForm.get('name')?.hasError('required')) {
<mat-error> The name is <strong>required</strong> </mat-error>
}
@if (participantForm.get('name')?.hasError('participantExists')) {
<mat-error>
The name is already taken. <strong> Please choose another name </strong>
</mat-error>
}
</mat-form-field>
<button
mat-raised-button
color="primary"
id="participant-name-submit"
type="submit"
class="join-button"
[disabled]="participantForm.invalid"
>
<span>Join Meeting</span>
</button>
</form>
</mat-card-content>
</mat-card>
<!-- View Recordings Card -->
@if (showRecordingCard) {
<mat-card class="action-card secondary-card fade-in-delayed">
<mat-card-header class="card-header">
<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>
</div>
</div>
</div>
} @else {

View File

@ -1,81 +1,264 @@
.form-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: var(--ov-meet-surface-background);
padding: 20px;
@import '../../../../../../src/assets/styles/design-tokens';
.card-wrapper {
display: flex;
gap: 40px;
flex-wrap: wrap;
justify-content: center;
align-items: flex-start;
// Room Access Container - Main layout using design tokens
.room-access-container {
@include ov-container;
@include ov-page-content;
min-height: 100vh;
padding-top: var(--ov-meet-spacing-xxl);
background: var(--ov-meet-background-color);
gap: 0;
}
// Room Header - Clean title section
.room-header {
@include ov-flex-center;
flex-direction: column;
gap: var(--ov-meet-spacing-md);
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);
}
.form-card {
width: 400px;
padding: 20px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
border-radius: var(--ov-meet-surface-radius);
background-color: var(--ov-meet-surface-primary);
.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;
flex-direction: column;
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 {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.form-title {
text-align: center;
margin-bottom: 20px;
color: var(--ov-meet-text-primary);
strong {
font-style: italic;
}
}
.full-width {
width: 100%;
margin-bottom: 20px;
}
button {
height: 56px;
border-radius: var(--ov-meet-surface-radius);
}
button:not([disabled]) {
background-color: var(--ov-meet-color-accent);
color: var(--ov-meet-text-on-accent);
// 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;
flex-direction: column;
gap: var(--ov-meet-spacing-lg);
flex: 1;
.name-field {
width: 100%;
.mat-mdc-form-field-icon-suffix {
color: var(--ov-meet-text-hint);
}
}
.join-button {
@include ov-button-base;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
gap: var(--ov-meet-spacing-sm);
margin-top: auto;
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 {
#media-buttons-container .custom-leave-btn > button {
&:hover {
background-color: var(--ov-error-color) !important;
background-color: var(--ov-meet-color-error) !important;
}
text-align: center;
background-color: var(--ov-error-color) !important;
color: var(--ov-secondary-action-color);
border-radius: var(--ov-leave-button-radius) !important;
background-color: var(--ov-meet-color-error) !important;
color: var(--ov-meet-text-on-primary);
border-radius: var(--ov-meet-radius-sm) !important;
width: 65px !important;
margin: 0px !important;
}

View File

@ -61,7 +61,7 @@ export class VideoRoomComponent implements OnInit, OnDestroy {
name: new FormControl('', [Validators.required, Validators.minLength(4)])
});
showRoom = false;
showRecordingCard = false;
roomId = '';
roomSecret = '';
participantName = '';
@ -90,6 +90,14 @@ export class VideoRoomComponent implements OnInit, OnDestroy {
this.roomId = this.roomService.getRoomId();
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();
}
@ -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) {
const message: WebComponentOutboundEventMessage = {
event: WebComponentEvent.JOIN,

View File

@ -7,6 +7,8 @@
--ov-meet-color-primary: #1976d2;
--ov-meet-color-primary-light: #42a5f5;
--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-light: #9c27b0;
--ov-meet-color-accent-dark: #6a1b9a;

View File

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