Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/lfx-pcc/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export const routes: Routes = [
canActivate: [authGuard],
},
{
path: 'meetings/:id',
loadComponent: () => import('./modules/meeting/meeting.component').then((m) => m.MeetingComponent),
path: 'meetings',
loadChildren: () => import('./modules/meeting/meeting.routes').then((m) => m.MEETING_ROUTES),
},
{
path: 'project/:slug',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@

<!-- Recurring Badge -->
@if (meeting().recurrence) {
<div class="inline-flex items-center gap-1 px-2 py-1 rounded text-xs font-medium bg-gray-400 text-white">
<div
class="inline-flex items-center gap-1 px-2 py-1 rounded text-xs font-medium bg-gray-400 text-white"
[pTooltip]="meeting().recurrence | recurrenceSummary"
data-testid="recurring-badge">
<i class="fa-light fa-repeat"></i>
<span>Recurring</span>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { HttpParams } from '@angular/common/http';
import { Component, computed, inject, signal, Signal, WritableSignal } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { LinkifyPipe } from '@app/shared/pipes/linkify.pipe';
import { RecurrenceSummaryPipe } from '@app/shared/pipes/recurrence-summary.pipe';
import { ButtonComponent } from '@components/button/button.component';
import { CardComponent } from '@components/card/card.component';
import { ExpandableTextComponent } from '@components/expandable-text/expandable-text.component';
Expand All @@ -20,10 +21,10 @@ import { UserService } from '@services/user.service';
import { MessageService } from 'primeng/api';
import { ToastModule } from 'primeng/toast';
import { TooltipModule } from 'primeng/tooltip';
import { combineLatest, finalize, map, of, switchMap, tap } from 'rxjs';
import { catchError, combineLatest, finalize, map, of, switchMap, tap } from 'rxjs';

@Component({
selector: 'lfx-meeting',
selector: 'lfx-meeting-join',
standalone: true,
imports: [
CommonModule,
Expand All @@ -34,16 +35,18 @@ import { combineLatest, finalize, map, of, switchMap, tap } from 'rxjs';
ToastModule,
TooltipModule,
MeetingTimePipe,
RecurrenceSummaryPipe,
LinkifyPipe,
ExpandableTextComponent,
],
providers: [MessageService],
templateUrl: './meeting.component.html',
templateUrl: './meeting-join.component.html',
})
export class MeetingComponent {
export class MeetingJoinComponent {
// Injected services
private readonly messageService = inject(MessageService);
private readonly activatedRoute = inject(ActivatedRoute);
private readonly router = inject(Router);
private readonly meetingService = inject(MeetingService);
private readonly userService = inject(UserService);

Expand Down Expand Up @@ -114,10 +117,21 @@ export class MeetingComponent {
const meetingId = params.get('id');
this.password.set(queryParams.get('password'));
if (meetingId) {
return this.meetingService.getPublicMeeting(meetingId, this.password());
return this.meetingService.getPublicMeeting(meetingId, this.password()).pipe(
catchError((error) => {
// If 404, navigate to not found page
if ([404, 403, 400].includes(error.status)) {
this.router.navigate(['/meetings/not-found']);
return of({} as { meeting: Meeting; project: Project });
}
// Re-throw other errors
throw error;
})
);
}

// TODO: If no meeting ID, redirect to 404
// If no meeting ID, redirect to not found
this.router.navigate(['/meetings/not-found']);
return of({} as { meeting: Meeting; project: Project });
}),
map((res) => ({ ...res.meeting, project: res.project })),
Expand Down Expand Up @@ -158,48 +172,24 @@ export class MeetingComponent {

private initializeMeetingTypeBadge(): Signal<{ badgeClass: string; icon?: string; text: string } | null> {
return computed(() => {
const meetingType = this.meeting()?.meeting_type || 'none';
const normalizedType = meetingType.toLowerCase();
const meetingType = this.meeting()?.meeting_type;
if (!meetingType) return null;

switch (normalizedType) {
const type = meetingType.toLowerCase();

switch (type) {
case 'board':
return {
badgeClass: 'bg-meeting-board text-white',
icon: 'fa-light fa-user-check',
text: 'Board',
};
return { badgeClass: 'bg-red-100 text-red-500', icon: 'fa-light fa-user-check', text: meetingType };
case 'maintainers':
return {
badgeClass: 'bg-meeting-maintainers text-white',
icon: 'fa-light fa-cog',
text: 'Maintainers',
};
return { badgeClass: 'bg-blue-100 text-blue-500', icon: 'fa-light fa-gear', text: meetingType };
case 'marketing':
return {
badgeClass: 'bg-meeting-marketing text-white',
icon: 'fa-light fa-chart-line',
text: 'Marketing',
};
return { badgeClass: 'bg-green-100 text-green-500', icon: 'fa-light fa-chart-line-up', text: meetingType };
case 'technical':
return {
badgeClass: 'bg-meeting-technical text-white',
icon: 'fa-light fa-code',
text: 'Technical',
};
return { badgeClass: 'bg-purple-100 text-purple-500', icon: 'fa-light fa-code', text: meetingType };
case 'legal':
return {
badgeClass: 'bg-meeting-legal text-white',
icon: 'fa-light fa-balance-scale',
text: 'Legal',
};
case 'other':
return {
badgeClass: 'bg-meeting-other text-white',
icon: 'fa-light fa-folder',
text: 'Other',
};
return { badgeClass: 'bg-amber-100 text-amber-500', icon: 'fa-light fa-scale-balanced', text: meetingType };
default:
return null;
return { badgeClass: 'bg-gray-100 text-gray-400', icon: 'fa-light fa-calendar-days', text: meetingType };
}
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<!-- Copyright The Linux Foundation and each contributor to LFX. -->
<!-- SPDX-License-Identifier: MIT -->

<div class="bg-gray-50">
<!-- Main Content -->
<div class="container mx-auto py-6 px-8">
<div class="max-w-4xl mx-auto">
<!-- Meeting Not Found Card -->
<lfx-card class="mb-6" data-testid="meeting-not-found-card">
<div class="text-center py-12">
<!-- Error Icon -->
<div class="flex justify-center mb-6">
<div class="w-24 h-24 bg-gray-100 rounded-full flex items-center justify-center">
<i class="fa-light fa-calendar-xmark text-4xl text-gray-400"></i>
</div>
</div>

<!-- Error Message -->
<h1 class="text-2xl font-display font-semibold text-gray-900 mb-3" data-testid="error-title">Meeting Not Found</h1>

<p class="text-gray-600 mb-6 max-w-lg mx-auto" data-testid="error-description">
We couldn't find the meeting you're looking for. This might happen for several reasons.
</p>

<!-- Possible Reasons -->
<div class="bg-gray-50 rounded-lg p-6 mb-8 text-left max-w-lg mx-auto">
<h3 class="text-sm font-medium text-gray-700 mb-3">Possible reasons:</h3>
<ul class="text-sm text-gray-600 space-y-2">
<li class="flex items-start gap-2">
<i class="fa-light fa-circle-dot text-gray-400 mt-1 text-xs"></i>
<span>The meeting link has expired or is no longer valid</span>
</li>
<li class="flex items-start gap-2">
<i class="fa-light fa-circle-dot text-gray-400 mt-1 text-xs"></i>
<span>The meeting has been deleted or cancelled</span>
</li>
<li class="flex items-start gap-2">
<i class="fa-light fa-circle-dot text-gray-400 mt-1 text-xs"></i>
<span>You may need the correct password to access this meeting</span>
</li>
<li class="flex items-start gap-2">
<i class="fa-light fa-circle-dot text-gray-400 mt-1 text-xs"></i>
<span>The meeting URL may contain a typo</span>
</li>
</ul>
</div>

<!-- Action Buttons -->
<div class="flex sm:items-centerflex-col sm:flex-row gap-6 justify-center">
<lfx-button routerLink="/" class="flex items-center" size="small" data-testid="go-home-button">
<i class="fa-light fa-home mr-2"></i>
<span>Go to Projects Dashboard</span>
</lfx-button>

<lfx-button
[href]="supportUrl"
class="flex items-center"
target="_blank"
rel="noopener noreferrer"
data-testid="contact-support-button"
icon="fa-light fa-life-ring"
label="Contact Support"
size="small"
severity="secondary">
</lfx-button>
</div>
</div>
</lfx-card>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright The Linux Foundation and each contributor to LFX.
// SPDX-License-Identifier: MIT

import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
import { ButtonComponent } from '@components/button/button.component';
import { CardComponent } from '@components/card/card.component';
import { environment } from '@environments/environment';

@Component({
selector: 'lfx-meeting-not-found',
standalone: true,
imports: [CommonModule, RouterLink, ButtonComponent, CardComponent],
templateUrl: './meeting-not-found.component.html',
})
export class MeetingNotFoundComponent {
public readonly supportUrl = environment.urls.support || 'mailto:[email protected]';
}
15 changes: 15 additions & 0 deletions apps/lfx-pcc/src/app/modules/meeting/meeting.routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright The Linux Foundation and each contributor to LFX.
// SPDX-License-Identifier: MIT

import { Routes } from '@angular/router';

export const MEETING_ROUTES: Routes = [
{
path: 'not-found',
loadComponent: () => import('./meeting-not-found/meeting-not-found.component').then((m) => m.MeetingNotFoundComponent),
},
{
path: ':id',
loadComponent: () => import('./meeting-join/meeting-join.component').then((m) => m.MeetingJoinComponent),
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { Router, RouterModule } from '@angular/router';
import { AvatarComponent } from '@components/avatar/avatar.component';
import { MenubarComponent } from '@components/menubar/menubar.component';
import { environment } from '@environments/environment';
import { Project } from '@lfx-pcc/shared/interfaces';
import { ProjectService } from '@services/project.service';
import { UserService } from '@services/user.service';
Expand Down Expand Up @@ -58,7 +57,6 @@ export class HeaderComponent {
{
label: 'Developer Settings',
icon: 'fa-light fa-cog',
url: environment.urls.profile + 'developer-settings',
target: '_blank',
},
{
Expand Down
2 changes: 1 addition & 1 deletion apps/lfx-pcc/src/environments/environment.dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export const environment = {
production: false,
urls: {
home: 'https://app.dev.lfx.dev',
profile: 'https://myprofile.dev.platform.linuxfoundation.org/',
support: 'https://jira.linuxfoundation.org/plugins/servlet/desk',
},
};
2 changes: 1 addition & 1 deletion apps/lfx-pcc/src/environments/environment.prod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export const environment = {
production: true,
urls: {
home: 'https://app.lfx.dev',
profile: 'https://openprofile.dev',
support: 'https://jira.linuxfoundation.org/plugins/servlet/desk',
},
};
3 changes: 2 additions & 1 deletion apps/lfx-pcc/src/environments/environment.staging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
export const environment = {
production: true,
urls: {
profile: 'https://myprofile.staging.platform.linuxfoundation.org/',
home: 'https://app.staging.lfx.dev',
support: 'https://jira.linuxfoundation.org/plugins/servlet/desk',
},
};
2 changes: 1 addition & 1 deletion apps/lfx-pcc/src/environments/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export const environment = {
production: false,
urls: {
home: 'http://localhost:4200',
profile: 'https://myprofile.dev.platform.linuxfoundation.org/',
support: 'https://jira.linuxfoundation.org/plugins/servlet/desk',
},
};