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: 4 additions & 0 deletions apps/lfx-one/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export const routes: Routes = [
path: 'groups',
loadChildren: () => import('./modules/committees/committees.routes').then((m) => m.COMMITTEE_ROUTES),
},
{
path: 'settings',
loadChildren: () => import('./modules/settings/settings.routes').then((m) => m.SETTINGS_ROUTES),
},
],
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ <h2 class="text-lg font-semibold text-gray-900">Menu</h2>
}

<!-- Main Content Area -->
<main class="flex-1 min-w-0 transition-all duration-300 lg:ml-64 px-[21px] md:px-[32px] py-[24px]" data-testid="main-content">
<router-outlet />
<main class="flex-1 min-w-0 transition-all duration-300 lg:ml-64 flex flex-col" data-testid="main-content">
<div class="flex-grow px-5 md:px-8 py-6">
<router-outlet />
</div>
<!-- Footer Component -->
<lfx-footer class="py-8"></lfx-footer>
<lfx-footer class="py-8 px-5 md:px-8 mt-auto"></lfx-footer>
</main>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { SidebarComponent } from '@components/sidebar/sidebar.component';
import { COMMITTEE_LABEL } from '@lfx-one/shared/constants';
import { SidebarMenuItem } from '@lfx-one/shared/interfaces';
import { PersonaService } from '@services/persona.service';
import { ProjectContextService } from '@services/project-context.service';
import { filter } from 'rxjs';

@Component({
Expand All @@ -26,12 +27,14 @@ export class MainLayoutComponent {
private readonly appService = inject(AppService);
private readonly featureFlagService = inject(FeatureFlagService);
private readonly personaService = inject(PersonaService);
private readonly projectContextService = inject(ProjectContextService);

// Expose mobile sidebar state from service
protected readonly selectedProject = this.projectContextService.selectedProject;
protected readonly showMobileSidebar = this.appService.showMobileSidebar;

// Feature flags
private readonly showProjectsInSidebar = this.featureFlagService.getBooleanFlag('sidebar-projects', true);
private readonly showProjectsInSidebar = this.featureFlagService.getBooleanFlag('sidebar-projects', false);

// Base sidebar navigation items - matching React NavigationSidebar design
private readonly baseSidebarItems: SidebarMenuItem[] = [
Expand Down Expand Up @@ -79,7 +82,6 @@ export class MainLayoutComponent {
label: 'Settings',
icon: 'fa-light fa-gear',
routerLink: '/settings',
disabled: true,
},
{
label: 'Profile',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,93 +4,6 @@
<!-- Header Component -->
<lfx-header></lfx-header>

<div class="bg-white border-b border-gray-100 py-6 shadow-sm min-h-60">
<div class="container mx-auto px-8">
<!-- Breadcrumb Navigation -->
<div class="mb-4">
<lfx-breadcrumb [model]="breadcrumbItems()">
<ng-template #item let-item>
<a [routerLink]="item.routerLink" class="flex items-center gap-2 text-sm font-medium text-gray-900 hover:text-gray-600 transition-colors">
@if (item.icon) {
<i [class]="item.icon" class="text-gray-500"></i>
}
{{ item.label }}
</a>
</ng-template>
</lfx-breadcrumb>
</div>

<div class="flex flex-col md:flex-row md:items-center justify-between w-full">
<div class="flex justify-between items-start gap-3 w-full">
<div class="flex flex-col gap-1 w-full">
<!-- Project Title -->
<h1 class="text-2xl font-display font-semibold text-gray-900 mb-3">
{{ projectTitle() }}
</h1>

<!-- Project Description -->
@if (projectDescription(); as description) {
<p class="text-gray-600 text-sm mb-6 max-w-3xl leading-relaxed">
{{ description }}
</p>
}
</div>

<!-- Category Badge -->
@if (categoryLabel(); as category) {
<div class="flex justify-start">
<img [src]="projectLogo()" class="h-16 w-full" />
</div>
}
</div>
</div>

<!-- Menu Items Section -->
<div class="md:flex items-center justify-start md:justify-between">
<div class="flex items-center gap-3 flex-wrap">
@for (menu of menuItems(); track menu.label) {
<a
[routerLink]="menu.routerLink"
class="pill"
routerLinkActive="bg-blue-50 text-blue-600 border-0"
data-testid="menu-item"
[routerLinkActiveOptions]="menu.routerLinkActiveOptions">
@if (menu.icon) {
<i [class]="menu.icon"></i>
}
{{ menu.label }}
</a>
}
@if (hasWriterAccess()) {
<div class="flex md:hidden">
<a
routerLink="/project/{{ projectSlug() }}/settings"
class="pill"
routerLinkActive="bg-blue-50 text-blue-600 border-0 block md:hidden"
data-testid="mobile-menu-item"
[routerLinkActiveOptions]="{ exact: true }">
<i class="fa-light fa-gear"></i>
<span>Settings</span>
</a>
</div>
}
</div>
@if (hasWriterAccess()) {
<div class="items-center gap-3 hidden md:flex">
<a
routerLink="/project/{{ projectSlug() }}/settings"
class="pill"
routerLinkActive="bg-blue-50 text-blue-600 border-0"
data-testid="menu-item"
[routerLinkActiveOptions]="{ exact: true }">
<i class="fa-light fa-gear"></i>
<span>Settings</span>
</a>
</div>
}
</div>
</div>
</div>
@if (project()) {
<router-outlet></router-outlet>
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { CommonModule } from '@angular/common';
import { Component, computed, inject, input, Signal, signal } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { ActivatedRoute, RouterModule } from '@angular/router';
import { BreadcrumbComponent } from '@components/breadcrumb/breadcrumb.component';
import { FilterButton, Project } from '@lfx-one/shared/interfaces';
import { ProjectService } from '@services/project.service';
import { HeaderComponent } from '@shared/components/header/header.component';
Expand All @@ -16,7 +15,7 @@ import { of, switchMap } from 'rxjs';
@Component({
selector: 'lfx-project-layout',
standalone: true,
imports: [CommonModule, RouterModule, HeaderComponent, BreadcrumbComponent, ChipModule],
imports: [CommonModule, RouterModule, HeaderComponent, ChipModule],
templateUrl: './project-layout.component.html',
styleUrl: './project-layout.component.scss',
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@
<p class="text-gray-600">Loading {{ committeeLabel.toLowerCase() }} details...</p>
</div>
</div>
}

@if (committees() && !committeesLoading()) {
} @else {
<div class="w-full space-y-6">
<!-- Page Title with Create Group Button -->
<div class="flex items-center justify-between">
<h1 class="font-['Inter'] text-xl font-semibold">{{ committeeLabel }}</h1>
<h1 class="font-inter text-xl font-semibold">{{ committeeLabelPlural }}</h1>
@if (canCreateGroup()) {
<lfx-button
[label]="'Create ' + committeeLabel"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<!-- Left Column - Basic Information -->
<div class="space-y-6">
<h2 class="text-lg mb-4" style="font-family: Inter, sans-serif; font-weight: 400">Basic Information</h2>
<h2 class="text-lg mb-4 font-inter font-normal">Basic Information</h2>

<!-- Committee Name -->
<div class="flex flex-col gap-2">
Expand Down Expand Up @@ -88,7 +88,7 @@ <h2 class="text-lg mb-4" style="font-family: Inter, sans-serif; font-weight: 400

<!-- Right Column - Settings -->
<div class="space-y-6">
<h2 class="text-lg mb-4" style="font-family: Inter, sans-serif; font-weight: 400">Settings</h2>
<h2 class="text-lg mb-4 font-inter font-normal">Settings</h2>

<div class="space-y-4">
<!-- Business Email Required -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
{{ committee.category || 'Other' }}
</span>
<div class="flex items-center gap-2">
<p class="font-['Inter:Medium',sans-serif] font-medium leading-[16px] not-italic text-[#0f172b] text-[12px] text-nowrap whitespace-pre">
<p class="font-inter font-medium leading-[16px] not-italic text-[#0f172b] text-[12px] text-nowrap whitespace-pre">
{{ committee.name }}
</p>
@if (!committee.public) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@

import { CommonModule } from '@angular/common';
import { Component, computed, inject, signal, Signal, WritableSignal } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
import { MeetingCardComponent } from '@app/shared/components/meeting-card/meeting-card.component';
import { ProjectContextService } from '@app/shared/services/project-context.service';
import { ButtonComponent } from '@components/button/button.component';
import { Meeting, ProjectContext } from '@lfx-one/shared/interfaces';
import { getCurrentOrNextOccurrence } from '@lfx-one/shared/utils';
import { MeetingService } from '@services/meeting.service';
import { PersonaService } from '@services/persona.service';
import { BehaviorSubject, map, switchMap, tap } from 'rxjs';
import { BehaviorSubject, catchError, map, merge, of, switchMap, tap } from 'rxjs';

import { MeetingsTopBarComponent } from './components/meetings-top-bar/meetings-top-bar.component';

Expand Down Expand Up @@ -40,18 +40,24 @@ export class MeetingsDashboardComponent {
public isNonFoundationProjectSelected: Signal<boolean>;

public constructor() {
// Initialize project context first (needed for reactive data loading)
this.project = computed(() => this.projectContextService.selectedProject() || this.projectContextService.selectedFoundation());

// Initialize permission checks
this.isMaintainer = computed(() => this.personaService.currentPersona() === 'maintainer');
this.isNonFoundationProjectSelected = computed(() => this.projectContextService.selectedProject() !== null);

// Initialize state
this.meetingsLoading = signal<boolean>(true);
this.refresh$ = new BehaviorSubject<void>(undefined);
this.meetings = this.initializeMeetings();
this.currentView = signal<'list' | 'calendar'>('list');
this.searchQuery = signal<string>('');
this.timeFilter = signal<'upcoming' | 'past'>('upcoming');
this.topBarVisibilityFilter = signal<'mine' | 'public'>('mine');

// Initialize data with reactive pattern
this.meetings = this.initializeMeetings();
this.filteredMeetings = this.initializeFilteredMeetings();
this.project = computed(() => this.projectContextService.selectedProject() || this.projectContextService.selectedFoundation());
this.isMaintainer = computed(() => this.personaService.currentPersona() === 'maintainer');
// A non-foundation project is selected if selectedProject is not null
this.isNonFoundationProjectSelected = computed(() => this.projectContextService.selectedProject() !== null);
}

public onViewChange(view: 'list' | 'calendar'): void {
Expand All @@ -64,10 +70,23 @@ export class MeetingsDashboardComponent {
}

private initializeMeetings(): Signal<Meeting[]> {
// Convert project signal to observable to react to project changes
const project$ = toObservable(this.project);

return toSignal(
this.refresh$.pipe(
switchMap(() =>
this.meetingService.getMeetings().pipe(
merge(
project$, // Triggers on project context changes
this.refresh$ // Triggers on manual refresh
).pipe(
tap(() => this.meetingsLoading.set(true)),
switchMap(() => {
const project = this.project();
if (!project?.projectId) {
this.meetingsLoading.set(false);
return of([]);
}

return this.meetingService.getMeetings().pipe(
map((meetings) => {
// Sort meetings by current or next occurrence start time (earliest first)
return meetings.sort((a, b) => {
Expand All @@ -87,13 +106,16 @@ export class MeetingsDashboardComponent {
return new Date(occurrenceA.start_time).getTime() - new Date(occurrenceB.start_time).getTime();
});
}),
catchError((error) => {
console.error('Failed to load meetings:', error);
this.meetingsLoading.set(false);
return of([]);
}),
tap(() => this.meetingsLoading.set(false))
)
)
);
})
),
{
initialValue: [],
}
{ initialValue: [] }
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<div class="flex justify-center items-center min-h-96">
<div class="text-center">
<i class="fa-light fa-spinner-third fa-spin text-3xl text-blue-600 mb-4"></i>
<p class="text-gray-600">Loading committee details...</p>
<p class="text-gray-600">Loading group details...</p>
</div>
</div>
}
Expand All @@ -17,9 +17,9 @@
<div class="flex justify-center items-center min-h-96">
<div class="text-center">
<i class="fa-light fa-exclamation-triangle text-3xl text-red-500 mb-4"></i>
<h2 class="text-xl font-semibold text-gray-900 mb-2">Committee Not Found</h2>
<p class="text-gray-600 mb-4">The committee you're looking for doesn't exist or has been removed.</p>
<lfx-button label="Back to Committees" styleClass="p-button-outlined" (onClick)="goBack()"> </lfx-button>
<h2 class="text-xl font-semibold text-gray-900 mb-2">Group Not Found</h2>
<p class="text-gray-600 mb-4">The group you're looking for doesn't exist or has been removed.</p>
<lfx-button label="Back to Groups" styleClass="p-button-outlined" (onClick)="goBack()"> </lfx-button>
</div>
</div>
}
Expand All @@ -33,7 +33,7 @@ <h2 class="text-xl font-semibold text-gray-900 mb-2">Committee Not Found</h2>
<div class="flex items-center gap-3 mb-2">
<button (click)="goBack()" class="inline-flex items-center text-gray-500 hover:text-gray-700 transition-colors">
<i class="fa-light fa-arrow-left mr-2"></i>
Back to Committees
Back to Groups
</button>
</div>
<h1 class="text-3xl font-bold text-gray-900 mb-2">{{ committee()?.name }}</h1>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,7 @@ export class CommitteeViewComponent {
}

public goBack(): void {
const project = this.project();
if (project) {
this.router.navigate(['/project', project.slug, 'committees']);
}
this.router.navigate(['/', 'groups']);
}

public toggleActionMenu(event: Event, menuComponent: MenuComponent): void {
Expand Down Expand Up @@ -155,7 +152,7 @@ export class CommitteeViewComponent {
summary: 'Error',
detail: 'Failed to load committee',
});
this.router.navigate(['/project', this.project()!.slug, 'committees']);
this.router.navigate(['/', 'groups']);
return throwError(() => new Error('Failed to load committee'));
})
);
Expand Down
Loading