frontend: implement share meeting link component and integrate it into the meeting page

This commit is contained in:
Carlos Santos 2025-07-31 10:52:19 +02:00
parent 2f7934c2a8
commit cc858a6d2c
7 changed files with 140 additions and 86 deletions

View File

@ -0,0 +1,10 @@
<div class="meeting-url-badge fade-in-delayed-more">
<div class="meeting-url-badge-title">Invite others with this meeting link</div>
<div class="meeting-url-badge-container" (click)="copyClicked.emit()">
<span class="meeting-url-text">{{ meetingUrl }}</span>
<button matIconButton class="copy-url-btn" matTooltip="Copy meeting link">
<mat-icon>content_copy</mat-icon>
</button>
</div>
</div>

View File

@ -0,0 +1,75 @@
@import '../../../../../../src/assets/styles/design-tokens';
.meeting-url-badge {
margin: var(--ov-meet-spacing-sm) auto;
.meeting-url-badge-title {
text-align: center;
font-size: var(--ov-meet-font-size-sm);
font-weight: var(--ov-meet-font-weight-light);
color: var(--ov-meet-text-primary);
margin-bottom: var(--ov-meet-spacing-sm);
}
.meeting-url-badge-container {
@include ov-flex-center;
@include ov-theme-transition;
gap: var(--ov-meet-spacing-sm);
padding: var(--ov-meet-spacing-sm) var(--ov-meet-spacing-md);
background-color: var(--ov-meet-surface-color);
border: 1px solid var(--ov-meet-border-color-light);
border-radius: var(--ov-meet-radius-lg);
max-width: fit-content;
margin: auto;
cursor: pointer;
&:hover {
background-color: var(--ov-meet-surface-hover);
border-color: var(--ov-meet-border-color);
}
.meeting-url-text {
font-family: var(--ov-meet-font-family-mono, 'Roboto Mono', monospace);
font-size: var(--ov-meet-font-size-sm);
color: var(--ov-meet-text-secondary);
font-weight: var(--ov-meet-font-weight-medium);
letter-spacing: 0.025em;
user-select: none;
word-break: break-all;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.copy-url-btn {
@include ov-button-base;
width: 32px;
height: 32px;
min-width: 32px;
padding: 0;
color: var(--ov-meet-text-hint);
cursor: pointer;
background: transparent;
border: 0;
.mat-icon {
@include ov-icon(sm);
}
}
}
}
@include ov-mobile-down {
.meeting-url-badge-container {
margin: var(--ov-meet-spacing-md) auto;
padding: var(--ov-meet-spacing-xs) var(--ov-meet-spacing-sm);
.meeting-url-text {
font-size: var(--ov-meet-font-size-xs);
}
.copy-url-btn {
width: 28px;
height: 28px;
min-width: 28px;
}
}
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ShareMeetingLinkComponent } from './share-meeting-link.component';
describe('ShareMeetingLinkComponent', () => {
let component: ShareMeetingLinkComponent;
let fixture: ComponentFixture<ShareMeetingLinkComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ShareMeetingLinkComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ShareMeetingLinkComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,17 @@
import { Component } from '@angular/core';
import { Input } from '@angular/core';
import { Output, EventEmitter } from '@angular/core';
import { MatButtonModule, MatIconButton } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
@Component({
selector: 'ov-share-meeting-link',
standalone: true,
imports: [MatButtonModule, MatIconModule, MatIconButton],
templateUrl: './share-meeting-link.component.html',
styleUrl: './share-meeting-link.component.scss'
})
export class ShareMeetingLinkComponent {
@Input() meetingUrl!: string;
@Output() copyClicked = new EventEmitter<void>();
}

View File

@ -192,21 +192,10 @@
<!-- Room URL Badge -->
@if (features().canModerateRoom) {
<div class="room-url-badge fade-in-delayed-more">
<div class="room-url-badge-title">Invite others with this meeting link</div>
<div class="room-url-badge-container">
<span class="room-url-text">{{ hostname }}/{{ roomId }}</span>
<button
mat-icon-button
(click)="copyPublisherLink()"
class="copy-url-btn"
matTooltip="Copy room link"
>
<mat-icon>content_copy</mat-icon>
</button>
</div>
</div>
<ov-share-meeting-link
[meetingUrl]="hostname + '/' + roomId"
(copyClicked)="copyPublisherLink()"
></ov-share-meeting-link>
}
<!-- Quick Actions -->

View File

@ -195,57 +195,6 @@
margin-top: auto;
}
// Room URL Badge
.room-url-badge {
margin: var(--ov-meet-spacing-sm) auto;
.room-url-badge-title {
text-align: center;
font-size: var(--ov-meet-font-size-sm);
font-weight: var(--ov-meet-font-weight-light);
color: var(--ov-meet-text-primary);
margin-bottom: var(--ov-meet-spacing-sm);
}
.room-url-badge-container {
@include ov-flex-center;
gap: var(--ov-meet-spacing-sm);
padding: var(--ov-meet-spacing-sm) var(--ov-meet-spacing-md);
background-color: var(--ov-meet-surface-color);
border: 1px solid var(--ov-meet-border-color-light);
border-radius: var(--ov-meet-radius-lg);
max-width: fit-content;
@include ov-theme-transition;
&:hover {
background-color: var(--ov-meet-surface-hover);
border-color: var(--ov-meet-border-color);
}
.room-url-text {
font-family: var(--ov-meet-font-family-mono, 'Roboto Mono', monospace);
font-size: var(--ov-meet-font-size-sm);
color: var(--ov-meet-text-secondary);
font-weight: var(--ov-meet-font-weight-medium);
letter-spacing: 0.025em;
user-select: all;
cursor: text;
}
.copy-url-btn {
@include ov-button-base;
width: 32px;
height: 32px;
min-width: 32px;
padding: 0;
color: var(--ov-meet-text-hint);
@include ov-theme-transition;
.mat-icon {
@include ov-icon(sm);
}
}
}
}
// Quick Actions - Footer actions
.quick-actions {
@include ov-flex-center;
@ -265,6 +214,10 @@
}
}
.share-meeting-link-container {
padding: 10px;
}
// Responsive adjustments
@include ov-mobile-down {
.room-access-container {
@ -297,21 +250,6 @@
padding: var(--ov-meet-spacing-md);
}
}
.room-url-badge-container {
margin: var(--ov-meet-spacing-md) auto;
padding: var(--ov-meet-spacing-xs) var(--ov-meet-spacing-sm);
.room-url-text {
font-size: var(--ov-meet-font-size-xs);
}
.copy-url-btn {
width: 28px;
height: 28px;
min-width: 28px;
}
}
}
// Custom leave button styling (existing functionality)

View File

@ -11,6 +11,7 @@ import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatTooltipModule } from '@angular/material/tooltip';
import { ActivatedRoute } from '@angular/router';
import { ShareMeetingLinkComponent } from '@lib/components/share-meeting-link/share-meeting-link.component';
import { ErrorReason } from '@lib/models';
import {
AppDataService,
@ -69,7 +70,8 @@ import {
MatMenuModule,
MatDividerModule,
MatTooltipModule,
MatRippleModule
MatRippleModule,
ShareMeetingLinkComponent
]
})
export class MeetingComponent implements OnInit {
@ -113,6 +115,10 @@ export class MeetingComponent implements OnInit {
this.features = this.featureConfService.features;
}
get hostname(): string {
return window.location.origin.replace('http://', '').replace('https://', '');
}
async ngOnInit() {
this.roomId = this.roomService.getRoomId();
this.roomSecret = this.roomService.getRoomSecret();
@ -122,10 +128,6 @@ export class MeetingComponent implements OnInit {
await this.initializeParticipantName();
}
get hostname(): string {
return window.location.origin.replace('http://', '').replace('https://', '');
}
/**
* Sets the back button text based on the application mode and user role
*/