frontend: enhance share recording dialog with error handling and clipboard support

This commit is contained in:
juancarmore 2025-06-17 15:11:16 +02:00
parent 110f66aca2
commit 253c7cb37b
3 changed files with 85 additions and 27 deletions

View File

@ -1,21 +1,42 @@
<h2 mat-dialog-title>Share Recording</h2> <h2 mat-dialog-title>Share Recording</h2>
<mat-dialog-content class="dialog-content"> <mat-dialog-content class="dialog-content">
<mat-radio-group [(ngModel)]="accessType"> @if (!recordingUrl) {
<mat-radio-button value="private">Private (authenticated users)</mat-radio-button> @if (erroMessage) {
<mat-radio-button value="public">Public (anyone with the link)</mat-radio-button> <div class="error-text">
</mat-radio-group> <mat-icon color="warn">error</mat-icon>
{{ erroMessage }}
</div>
}
<div class="generate-btn-container"> <mat-radio-group [(ngModel)]="accessType">
<button mat-raised-button color="primary" (click)="getRecordingUrl()">Generate Link</button> <mat-radio-button value="public">Public (anyone with the link)</mat-radio-button>
</div> <mat-radio-button value="private">Private (authenticated users)</mat-radio-button>
</mat-radio-group>
<div class="generate-btn-container">
@if (loading) {
<mat-spinner diameter="24"></mat-spinner>
} @else {
<button mat-flat-button color="primary" (click)="getRecordingUrl()" [disabled]="loading">
Generate Link
</button>
}
</div>
}
@if (recordingUrl) { @if (recordingUrl) {
<div class="recording-url-container"> <div class="recording-url-container">
<mat-form-field appearance="outline" class="url-field"> <mat-form-field appearance="outline" class="url-field">
<mat-label>Shareable URL</mat-label> <mat-label>Shareable URL</mat-label>
<input matInput [value]="recordingUrl" readonly /> <input matInput [value]="recordingUrl" readonly />
<button mat-icon-button matSuffix (click)="copyToClipboard()"> <button
<mat-icon>content_copy</mat-icon> mat-icon-button
matSuffix
(click)="copyToClipboard()"
[matTooltip]="copied ? 'Copied!' : 'Copy to clipboard'"
matTooltipPosition="above"
>
<mat-icon>{{ copied ? 'check' : 'content_copy' }}</mat-icon>
</button> </button>
</mat-form-field> </mat-form-field>
</div> </div>

View File

@ -3,6 +3,7 @@
flex-direction: column; flex-direction: column;
gap: 20px; gap: 20px;
min-width: 100%; min-width: 100%;
overflow: hidden;
mat-radio-group { mat-radio-group {
display: flex; display: flex;
@ -10,20 +11,26 @@
gap: 10px; gap: 10px;
mat-radio-button { mat-radio-button {
color: var(--ov-text-primary-color); color: var(--ov-text-surface-color);
} }
} }
} }
.generate-btn-container { .error-text {
display: flex; display: flex;
justify-content: flex-start; align-items: center;
gap: 6px;
color: var(--ov-error-color);
font-weight: 500;
}
.generate-btn-container {
align-self: flex-start;
} }
.recording-url-container { .recording-url-container {
display: flex; width: 100%;
flex-direction: column; margin-top: 8px;
gap: 10px;
} }
.url-field { .url-field {
@ -31,6 +38,6 @@
input { input {
font-size: 14px; font-size: 14px;
color: var(--ov-text-primary-color); color: var(--ov-text-surface-color);
} }
} }

View File

@ -1,3 +1,4 @@
import { Clipboard } from '@angular/cdk/clipboard';
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
@ -11,7 +12,9 @@ import {
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatRadioModule } from '@angular/material/radio'; import { MatRadioModule } from '@angular/material/radio';
import { MatTooltipModule } from '@angular/material/tooltip';
import { HttpService } from 'shared-meet-components'; import { HttpService } from 'shared-meet-components';
@Component({ @Component({
@ -27,29 +30,56 @@ import { HttpService } from 'shared-meet-components';
MatDialogTitle, MatDialogTitle,
MatDialogContent, MatDialogContent,
MatDialogActions, MatDialogActions,
MatDialogClose MatDialogClose,
MatTooltipModule,
MatProgressSpinnerModule
], ],
templateUrl: './share-recording-dialog.component.html', templateUrl: './share-recording-dialog.component.html',
styleUrl: './share-recording-dialog.component.scss' styleUrl: './share-recording-dialog.component.scss'
}) })
export class ShareRecordingDialogComponent { export class ShareRecordingDialogComponent {
accessType: 'private' | 'public' = 'private'; accessType: 'private' | 'public' = 'public';
recordingUrl: string | undefined; recordingUrl?: string;
loading = false;
erroMessage?: string;
copied = false;
constructor( constructor(
@Inject(MAT_DIALOG_DATA) public data: { recordingId: string }, @Inject(MAT_DIALOG_DATA) public data: { recordingId: string; recordingUrl?: string },
private httpService: HttpService private httpService: HttpService,
) {} private clipboard: Clipboard
) {
this.recordingUrl = data.recordingUrl;
}
async getRecordingUrl() { async getRecordingUrl() {
const privateAccess = this.accessType === 'private'; this.loading = true;
const { url } = await this.httpService.generateRecordingUrl(this.data.recordingId, privateAccess); this.erroMessage = undefined;
this.recordingUrl = url;
try {
const privateAccess = this.accessType === 'private';
const { url } = await this.httpService.generateRecordingUrl(this.data.recordingId, privateAccess);
this.recordingUrl = url;
} catch (error) {
this.erroMessage = 'Failed to generate recording URL. Please try again later.';
console.error('Error generating recording URL:', error);
} finally {
this.loading = false;
}
} }
copyToClipboard() { copyToClipboard() {
if (this.recordingUrl) { if (!this.recordingUrl) {
navigator.clipboard.writeText(this.recordingUrl); return;
} }
this.clipboard.copy(this.recordingUrl!);
this.copied = true;
// Reset copied state after 2 seconds
setTimeout(() => {
this.copied = false;
}, 2000);
} }
} }