frontend: Enhance Disconnected Component with user-friendly messages and animations
- Updated HTML structure for improved layout and branding. - Added disconnect reason handling in TypeScript for better user feedback. - Enhanced SCSS styles for a more visually appealing disconnected state. - Introduced scaleIn animation for a smoother user experience.
This commit is contained in:
parent
fbcb70dbc2
commit
4691888309
@ -1,10 +1,22 @@
|
||||
<div class="disconnected-container">
|
||||
<mat-card class="disconnected-card">
|
||||
<mat-card-header>
|
||||
<mat-card-title>You have left the room</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<p>The meeting has ended.</p>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<div class="disconnect-content" role="main" aria-labelledby="disconnect-title">
|
||||
<div class="disconnect-icon-container">
|
||||
<img
|
||||
src="data:image/png;base64,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"
|
||||
alt="Brand Logo"
|
||||
class="logo-image"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="disconnect-message">
|
||||
<h1 id="disconnect-title" class="disconnect-title">Meeting Ended</h1>
|
||||
<p class="disconnect-subtitle">
|
||||
{{ disconnectReason || 'You have successfully left the video conference' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="disconnect-footer">
|
||||
<p class="footer-text">Thank you for using OpenVidu Meet</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,33 +1,153 @@
|
||||
@import '../../../../../../src/assets/styles/design-tokens';
|
||||
|
||||
.disconnected-container {
|
||||
@include ov-theme-transition;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-color: var(--ov-meet-surface-background);
|
||||
min-height: 100vh;
|
||||
padding: var(--ov-meet-spacing-lg);
|
||||
background: linear-gradient(135deg, var(--ov-meet-background-color) 0%, var(--ov-meet-background-secondary) 100%);
|
||||
}
|
||||
|
||||
.disconnected-card {
|
||||
width: 400px;
|
||||
padding: 20px;
|
||||
.disconnect-content {
|
||||
@include ov-card;
|
||||
max-width: 520px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
background-color: var(--ov-meet-surface-primary);
|
||||
border-radius: var(--ov-meet-surface-radius);
|
||||
padding: var(--ov-meet-spacing-xxl);
|
||||
background: var(--ov-meet-surface-elevated);
|
||||
box-shadow: var(--ov-meet-shadow-lg);
|
||||
|
||||
@include ov-mobile-down {
|
||||
padding: var(--ov-meet-spacing-xl);
|
||||
margin: var(--ov-meet-spacing-md);
|
||||
max-width: calc(100vw - 2 * var(--ov-meet-spacing-md));
|
||||
}
|
||||
}
|
||||
|
||||
mat-card-header {
|
||||
.disconnect-icon-container {
|
||||
margin-bottom: var(--ov-meet-spacing-xl);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.logo-image {
|
||||
width: 50px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
transition: all var(--ov-meet-transition-normal);
|
||||
|
||||
@include ov-mobile-down {
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: rgba(25, 118, 210, 0.1);
|
||||
border-radius: var(--ov-meet-radius-circle);
|
||||
z-index: -1;
|
||||
|
||||
@include ov-mobile-down {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
}
|
||||
|
||||
position: relative;
|
||||
}
|
||||
|
||||
mat-card-title {
|
||||
font-size: 1.5em;
|
||||
.disconnect-message {
|
||||
margin-bottom: var(--ov-meet-spacing-xl);
|
||||
|
||||
.disconnect-title {
|
||||
font-size: var(--ov-meet-font-size-xxl);
|
||||
font-weight: var(--ov-meet-font-weight-semibold);
|
||||
color: var(--ov-meet-text-primary);
|
||||
margin: 0 0 var(--ov-meet-spacing-sm) 0;
|
||||
line-height: var(--ov-meet-line-height-tight);
|
||||
|
||||
@include ov-mobile-down {
|
||||
font-size: var(--ov-meet-font-size-xl);
|
||||
}
|
||||
}
|
||||
|
||||
.disconnect-subtitle {
|
||||
font-size: var(--ov-meet-font-size-md);
|
||||
color: var(--ov-meet-text-secondary);
|
||||
margin: 0;
|
||||
line-height: var(--ov-meet-line-height-normal);
|
||||
max-width: 400px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
@include ov-mobile-down {
|
||||
font-size: var(--ov-meet-font-size-sm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mat-card-content p {
|
||||
font-size: 1em;
|
||||
color: #555;
|
||||
.disconnect-footer {
|
||||
padding-top: var(--ov-meet-spacing-lg);
|
||||
border-top: 1px solid var(--ov-meet-border-color-light);
|
||||
|
||||
.footer-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--ov-meet-spacing-xs);
|
||||
font-size: var(--ov-meet-font-size-sm);
|
||||
color: var(--ov-meet-text-hint);
|
||||
margin: 0;
|
||||
font-weight: var(--ov-meet-font-weight-medium);
|
||||
}
|
||||
|
||||
@include ov-mobile-down {
|
||||
.footer-text {
|
||||
font-size: var(--ov-meet-font-size-xs);
|
||||
flex-direction: column;
|
||||
gap: var(--ov-meet-spacing-xs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mat-card-actions {
|
||||
margin-top: 20px;
|
||||
.disconnect-content {
|
||||
animation: fadeIn 0.6s ease-out;
|
||||
}
|
||||
|
||||
.disconnect-icon-container {
|
||||
animation: scaleIn 0.8s ease-out 0.2s both;
|
||||
}
|
||||
|
||||
|
||||
@media (prefers-contrast: high) {
|
||||
.disconnect-footer {
|
||||
border-top-width: 2px;
|
||||
}
|
||||
|
||||
.disconnect-icon-container::before {
|
||||
border: 2px solid var(--ov-meet-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.disconnect-content,
|
||||
.disconnect-icon-container {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.logo-image {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
.disconnect-icon-container::before {
|
||||
background: rgba(25, 118, 210, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,53 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'ov-disconnected',
|
||||
standalone: true,
|
||||
imports: [MatCardModule],
|
||||
imports: [CommonModule, MatCardModule, MatButtonModule, MatIconModule],
|
||||
templateUrl: './disconnected.component.html',
|
||||
styleUrl: './disconnected.component.scss'
|
||||
})
|
||||
export class DisconnectedComponent {}
|
||||
export class DisconnectedComponent implements OnInit {
|
||||
disconnectReason?: string;
|
||||
|
||||
constructor(private route: ActivatedRoute) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
// Get disconnect reason from query parameters
|
||||
this.getDisconnectReasonFromQueryParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the disconnect reason from URL query parameters
|
||||
*/
|
||||
private getDisconnectReasonFromQueryParams(): void {
|
||||
const reason = this.route.snapshot.queryParams['reason'];
|
||||
if (reason) {
|
||||
// Map technical reasons to user-friendly messages
|
||||
this.disconnectReason = this.mapReasonToUserMessage(reason);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps technical disconnect reasons to user-friendly messages
|
||||
*/
|
||||
private mapReasonToUserMessage(reason: string): string {
|
||||
const reasonMap: { [key: string]: string } = {
|
||||
disconnect: 'You have successfully disconnected from the meeting',
|
||||
forceDisconnectByUser: 'You were removed from the meeting by meeting host',
|
||||
forceDisconnectByServer: 'Your connection was terminated by the server',
|
||||
sessionClosedByServer: 'The meeting was ended by the host',
|
||||
networkDisconnect: 'Connection lost due to network connectivity issues',
|
||||
openviduDisconnect: 'The meeting ended due to technical difficulties',
|
||||
roomDeleted: 'The meeting room has been deleted',
|
||||
browserClosed: 'The meeting ended when your browser was closed'
|
||||
};
|
||||
|
||||
return reasonMap[reason] || reasonMap['disconnect'];
|
||||
}
|
||||
}
|
||||
|
||||
@ -267,7 +267,40 @@ export class VideoRoomComponent implements OnInit, OnDestroy {
|
||||
this.sessionStorageService.removeModeratorSecret(event.roomName);
|
||||
}
|
||||
|
||||
await this.navigationService.redirectTo(redirectURL, isExternalURL);
|
||||
// Add disconnect reason as query parameter if redirecting to disconnect page
|
||||
let finalRedirectURL = redirectURL;
|
||||
if (!isExternalURL && (redirectURL === '/disconnected' || redirectURL.includes('/disconnected'))) {
|
||||
const reasonParam = this.getReasonParamFromEvent(event.reason, isRoomDeleted);
|
||||
const separator = redirectURL.includes('?') ? '&' : '?';
|
||||
finalRedirectURL = `${redirectURL}${separator}reason=${encodeURIComponent(reasonParam)}`;
|
||||
}
|
||||
|
||||
await this.navigationService.redirectTo(finalRedirectURL, isExternalURL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps ParticipantLeftReason to a query parameter value
|
||||
*/
|
||||
private getReasonParamFromEvent(reason: ParticipantLeftReason, isRoomDeleted: boolean): string {
|
||||
if (isRoomDeleted) {
|
||||
return 'roomDeleted';
|
||||
}
|
||||
|
||||
switch (reason) {
|
||||
default:
|
||||
case ParticipantLeftReason.LEAVE:
|
||||
return 'disconnect';
|
||||
case ParticipantLeftReason.PARTICIPANT_REMOVED:
|
||||
return 'forceDisconnectByUser';
|
||||
case ParticipantLeftReason.SERVER_SHUTDOWN:
|
||||
return 'sessionClosedByServer';
|
||||
case ParticipantLeftReason.NETWORK_DISCONNECT:
|
||||
return 'networkDisconnect';
|
||||
case ParticipantLeftReason.SIGNAL_CLOSE:
|
||||
return 'openviduDisconnect';
|
||||
case ParticipantLeftReason.BROWSER_UNLOAD:
|
||||
return 'browserClosed';
|
||||
}
|
||||
}
|
||||
|
||||
async onRecordingStartRequested(event: RecordingStartRequestedEvent) {
|
||||
|
||||
@ -91,6 +91,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scaleIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Transition classes
|
||||
.fade-in {
|
||||
animation: fadeIn 0.6s ease-out forwards;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user