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
1 change: 1 addition & 0 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ jobs:
secret-ids: |
SUPABASE, /cloudops/managed-secrets/cloud/supabase/api_key
AUTH0, /cloudops/managed-secrets/auth0/LFX_V2_PCC
AUTH, /cloudops/managed-secrets/auth0/LFX_V2_Meeting_Join_M2M

- name: Validate required secrets for E2E testing
id: validate-secrets
Expand Down
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,43 @@ of conduct, development process, and how to submit pull requests.
2. **Configure required environment variables:**

**Auth0 Configuration:**
- Get the Auth0 Application values from 1Password
- Set `PCC_AUTH0_CLIENT_ID` and `PCC_AUTH0_CLIENT_SECRET`
- Local Development: The default client ID is `lfx` and you can get the client secret from the k8s via `k get secrets authelia-clients -n lfx -o jsonpath='{.data.lfx}' | base64 --decode`
- Update `PCC_AUTH0_ISSUER_BASE_URL` with your Auth0 domain
- Local Development: `https://auth.k8s.orb.local`
- Configure `PCC_AUTH0_AUDIENCE` for your API
- Local Development: `http://lfx-api.k8s.orb.local/`
- Set `PCC_AUTH0_SECRET` to a sufficiently long random string (32+ characters)
- Generate a random 32 characters long string

**M2M (Machine-to-Machine) Authentication:**
- Set `M2M_AUTH_CLIENT_ID` and `M2M_AUTH_CLIENT_SECRET` for server-side API calls
- Configure `M2M_AUTH_ISSUER_BASE_URL` (typically same as Auth0 base URL)
- Set `M2M_AUTH_AUDIENCE` to match your API audience

**Supabase Configuration:**
- Create a project in [Supabase](https://supabase.com)
- Get your project URL and anon key from Project Settings → API
- Set `SUPABASE_URL` and `POSTGRES_API_KEY`
- Configure `SUPABASE_STORAGE_BUCKET` for file storage

**Microservice Configuration:**
- Set `QUERY_SERVICE_URL` to your query service endpoint
- Provide a valid JWT token in `QUERY_SERVICE_TOKEN`
- Set `LFX_V2_SERVICE` to your query service endpoint
- Local Development: `http://lfx-api.k8s.orb.local`

**AI Service Configuration (Optional):**
- Set `AI_PROXY_URL` to your LiteLLM proxy endpoint for meeting agenda generation
- Provide a valid API key in `AI_API_KEY`

**NATS Configuration:**
- Set `NATS_URL` for internal messaging system (typically in Kubernetes environments)
- Local Development: `nats://lfx-platform-nats.lfx.svc.cluster.local:4222`

**Testing Configuration (Optional):**
- Set `TEST_USERNAME` and `TEST_PASSWORD` for automated E2E testing

**Local Development:**
- Set `NODE_TLS_REJECT_UNAUTHORIZED=0` when using Authelia for local authentication

#### Install and Run

Expand Down
2 changes: 1 addition & 1 deletion apps/lfx-pcc/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ENV=development
PCC_BASE_URL=http://localhost:4200
LOG_LEVEL=info

# Auth0 Authentication Configuration
# Auth0/Authelia Authentication Configuration
# Get these values from your Auth0 dashboard
PCC_AUTH0_CLIENT_ID=your-auth0-client-id
PCC_AUTH0_CLIENT_SECRET=your-auth0-client-secret
Expand Down
8 changes: 8 additions & 0 deletions apps/lfx-pcc/public/images/zoom-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions apps/lfx-pcc/src/app/app.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,12 @@
}
}
}

.p-menu {
.p-menu-item-link {
.p-menu-item-icon {
@apply w-5;
}
}
}
}
9 changes: 6 additions & 3 deletions apps/lfx-pcc/src/app/modules/meeting/meeting.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,9 @@ <h2 class="text-xl font-display font-semibold text-gray-900 mb-1 leading-tight"
<i class="fa-light fa-video text-gray-400"></i>
<span>Platform</span>
</div>
<div class="text-sm font-sans">Zoom Meeting</div>
<div class="text-xs text-gray-500">Link will be sent via email</div>
<div class="flex items-center gap-2">
<img src="/images/zoom-logo.svg" alt="Zoom" class="w-auto h-4 mt-2 object-contain" />
</div>
</div>

<!-- Duration Card -->
Expand Down Expand Up @@ -260,7 +261,9 @@ <h4 class="font-medium text-gray-900 font-sans">Enter your information</h4>

<div class="flex items-center justify-between pt-3">
<p class="text-sm text-gray-500 font-sans">Meeting invites and updates will be sent to your email</p>
<lfx-button size="small" [href]="meeting().join_url" severity="primary">Join Meeting</lfx-button>
<lfx-button size="small" [href]="meeting().join_url" severity="primary" label="Join Meeting" icon="fa-light fa-sign-in"
>Join Meeting</lfx-button
>
</div>
</form>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@ <h2 class="text-[16px] font-bold text-gray-900">Committee Management</h2>
Manage committees, their settings, and organizational structure. Sub-committees are indicated with indentation.
</p>
</div>
<lfx-button
label="New Committee"
icon="fa-light fa-plus"
severity="primary"
(onClick)="openCreateDialog()"
size="small"
data-testid="committee-new-cta">
</lfx-button>
@if (project()?.writer) {
<lfx-button
label="New Committee"
icon="fa-light fa-plus"
severity="primary"
(onClick)="openCreateDialog()"
size="small"
data-testid="committee-new-cta">
</lfx-button>
}
Comment on lines +23 to +32
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Gate the empty-state “Add First Committee” CTA too.

The bottom empty-state button (Lines 149–155) is still visible to non-writers; this is inconsistent with the new gating above.

Apply:

- <lfx-button
-   label="Add First Committee"
-   icon="fa-light fa-people-group"
-   severity="secondary"
-   (onClick)="openCreateDialog()"
-   data-testid="committee-add-first"></lfx-button>
+ @if (project()?.writer) {
+   <lfx-button
+     label="Add First Committee"
+     icon="fa-light fa-people-group"
+     severity="secondary"
+     (onClick)="openCreateDialog()"
+     data-testid="committee-add-first"></lfx-button>
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@if (project()?.writer) {
<lfx-button
label="New Committee"
icon="fa-light fa-plus"
severity="primary"
(onClick)="openCreateDialog()"
size="small"
data-testid="committee-new-cta">
</lfx-button>
}
@if (project()?.writer) {
<lfx-button
label="Add First Committee"
icon="fa-light fa-people-group"
severity="secondary"
(onClick)="openCreateDialog()"
data-testid="committee-add-first"></lfx-button>
}
🤖 Prompt for AI Agents
In
apps/lfx-pcc/src/app/modules/project/committees/committee-dashboard/committee-dashboard.component.html
around lines 149 to 155, the empty-state "Add First Committee" CTA is not gated
and remains visible to non-writers; wrap that button markup with the same writer
check used at the top (e.g., conditionally render the button only when
project()?.writer is true) so non-writers cannot see the empty-state CTA,
matching the gating applied to the top "New Committee" button.

</div>

<!-- Statistics Cards -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ export class CommitteeDashboardComponent {
public votingStatusOptions: Signal<{ label: string; value: string | null }[]>;
public filteredCommittees: Signal<Committee[]>;
public totalRecords: Signal<number>;
public menuItems: MenuItem[];
public actionMenuItems: MenuItem[];
public refresh: BehaviorSubject<void>;
private searchTerm: Signal<string>;
Expand Down Expand Up @@ -94,7 +93,6 @@ export class CommitteeDashboardComponent {
this.votingStatusOptions = this.initializeVotingStatusOptions();
this.filteredCommittees = this.initializeFilteredCommittees();
this.totalRecords = this.initializeTotalRecords();
this.menuItems = this.initializeMenuItems();
this.actionMenuItems = this.initializeActionMenuItems();
}

Expand Down Expand Up @@ -341,16 +339,6 @@ export class CommitteeDashboardComponent {
return computed(() => this.filteredCommittees().length);
}

private initializeMenuItems(): MenuItem[] {
return [
{
label: 'Create Committee',
icon: 'fa-light fa-users-medical text-sm',
command: () => this.openCreateDialog(),
},
];
}

private initializeActionMenuItems(): MenuItem[] {
return [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
<div class="flex justify-between gap-4 mb-8">
<h3 class="text-lg font-medium text-gray-900">Committee Members</h3>
<!-- Add Member Button -->
<lfx-button label="Add Member" icon="fa-light fa-user-plus" size="small" severity="secondary" (onClick)="openAddMemberDialog()"> </lfx-button>
@if (committee()?.writer) {
<lfx-button label="Add Member" icon="fa-light fa-user-plus" size="small" severity="secondary" (onClick)="openAddMemberDialog()"> </lfx-button>
}
</div>

<!-- Search and Filter Controls -->
Expand Down Expand Up @@ -136,14 +138,27 @@ <h3 class="text-lg font-medium text-gray-900">Committee Members</h3>
}
<td class="text-center relative">
<lfx-menu #memberActionMenu [model]="memberActionMenuItems" [popup]="true" appendTo="body"> </lfx-menu>
<lfx-button
icon="fa-light fa-ellipsis-vertical"
[text]="true"
[rounded]="true"
size="small"
severity="secondary"
(onClick)="toggleMemberActionMenu($event, member, memberActionMenu)">
</lfx-button>
@if (committee()?.writer) {
<lfx-button
icon="fa-light fa-ellipsis-vertical"
[text]="true"
[rounded]="true"
size="small"
severity="secondary"
(onClick)="toggleMemberActionMenu($event, member, memberActionMenu)">
</lfx-button>
} @else {
<lfx-button
icon="fa-light fa-envelope"
[text]="true"
[rounded]="true"
size="small"
severity="secondary"
[href]="`mailto:${member.email}`"
target="_blank"
pTooltip="Send Message">
</lfx-button>
}
Comment on lines +141 to +161
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard mailto when email is missing; add testids and aria-labels

Avoids mailto:undefined, improves testability and a11y.

-              } @else {
-                <lfx-button
-                  icon="fa-light fa-envelope"
-                  [text]="true"
-                  [rounded]="true"
-                  size="small"
-                  severity="secondary"
-                  [href]="`mailto:${member.email}`"
-                  target="_blank"
-                  pTooltip="Send Message">
-                </lfx-button>
-              }
+              } @else if (member.email) {
+                <lfx-button
+                  icon="fa-light fa-envelope"
+                  [text]="true"
+                  [rounded]="true"
+                  size="small"
+                  severity="secondary"
+                  [href]="`mailto:${member.email}`"
+                  target="_blank"
+                  pTooltip="Send Message"
+                  [attr.data-testid]="'committee-members-message-' + member.uid"
+                  [attr.aria-label]="'Send email to ' + (member.first_name || '') + ' ' + (member.last_name || '')">
+                </lfx-button>
+              } @else {
+                <lfx-button
+                  icon="fa-light fa-envelope-slash"
+                  [text]="true"
+                  [rounded]="true"
+                  size="small"
+                  severity="secondary"
+                  [disabled]="true"
+                  pTooltip="No email available"
+                  [attr.data-testid]="'committee-members-noemail-' + member.uid"
+                  [attr.aria-label]="'No email available'">
+                </lfx-button>
+              }

Also add testids/aria-labels to the ellipsis button:

-                <lfx-button
+                <lfx-button
                   icon="fa-light fa-ellipsis-vertical"
                   [text]="true"
                   [rounded]="true"
                   size="small"
                   severity="secondary"
-                  (onClick)="toggleMemberActionMenu($event, member, memberActionMenu)">
+                  (onClick)="toggleMemberActionMenu($event, member, memberActionMenu)"
+                  [attr.data-testid]="'committee-members-actions-' + member.uid"
+                  [attr.aria-label]="'Open actions for ' + (member.first_name || '') + ' ' + (member.last_name || '')">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@if (committee()?.writer) {
<lfx-button
icon="fa-light fa-ellipsis-vertical"
[text]="true"
[rounded]="true"
size="small"
severity="secondary"
(onClick)="toggleMemberActionMenu($event, member, memberActionMenu)">
</lfx-button>
} @else {
<lfx-button
icon="fa-light fa-envelope"
[text]="true"
[rounded]="true"
size="small"
severity="secondary"
[href]="`mailto:${member.email}`"
target="_blank"
pTooltip="Send Message">
</lfx-button>
}
@if (committee()?.writer) {
<lfx-button
icon="fa-light fa-ellipsis-vertical"
[text]="true"
[rounded]="true"
size="small"
severity="secondary"
(onClick)="toggleMemberActionMenu($event, member, memberActionMenu)"
[attr.data-testid]="'committee-members-actions-' + member.uid"
[attr.aria-label]="'Open actions for ' + (member.first_name || '') + ' ' + (member.last_name || '')">
</lfx-button>
} @else if (member.email) {
<lfx-button
icon="fa-light fa-envelope"
[text]="true"
[rounded]="true"
size="small"
severity="secondary"
[href]="`mailto:${member.email}`"
target="_blank"
pTooltip="Send Message"
[attr.data-testid]="'committee-members-message-' + member.uid"
[attr.aria-label]="'Send email to ' + (member.first_name || '') + ' ' + (member.last_name || '')">
</lfx-button>
} @else {
<lfx-button
icon="fa-light fa-envelope-slash"
[text]="true"
[rounded]="true"
size="small"
severity="secondary"
[disabled]="true"
pTooltip="No email available"
[attr.data-testid]="'committee-members-noemail-' + member.uid"
[attr.aria-label]="'No email available'">
</lfx-button>
}

</td>
</tr>
</ng-template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,17 @@
<!-- Actions column -->
<td class="px-4 py-3">
<div class="flex items-center justify-center gap-1">
<lfx-button
icon="fa-light fa-edit"
[text]="true"
[rounded]="true"
size="small"
severity="secondary"
styleClass="h-8 w-8 p-0 text-gray-500 hover:text-gray-700"
(onClick)="onEdit(committee)"
[attr.data-testid]="'committee-edit-' + committee.uid" />
@if (committee?.writer) {
<lfx-button
icon="fa-light fa-edit"
[text]="true"
[rounded]="true"
size="small"
severity="secondary"
styleClass="h-8 w-8 p-0 text-gray-500 hover:text-gray-700"
(onClick)="onEdit(committee)"
[attr.data-testid]="'committee-edit-' + committee.uid" />
}
<lfx-button
icon="fa-light fa-users"
[text]="true"
Expand All @@ -130,22 +132,25 @@
severity="secondary"
styleClass="h-8 w-8 p-0 text-gray-500 hover:text-gray-700"
(onClick)="onView(committee)"
[attr.data-testid]="'committee-members-' + committee.uid" />
<lfx-button
icon="fa-light fa-ellipsis"
[text]="true"
[rounded]="true"
size="small"
severity="secondary"
styleClass="h-8 w-8 p-0 text-gray-500 hover:text-gray-700"
(onClick)="toggleCommitteeActionMenu($event, committee, actionMenu)"
[attr.data-testid]="'committee-more-' + committee.uid" />
[attr.data-testid]="'committee-members-' + committee.uid"
pTooltip="View members" />
@if (committee?.writer) {
<lfx-button
icon="fa-light fa-ellipsis"
[text]="true"
[rounded]="true"
size="small"
severity="secondary"
styleClass="h-8 w-8 p-0 text-gray-500 hover:text-gray-700"
(onClick)="toggleCommitteeActionMenu($event, committee, actionMenu)"
[attr.data-testid]="'committee-more-' + committee.uid" />
}
</div>
</td>
</tr>
</ng-template>

<lfx-menu #actionMenu [model]="committeeActionMenuItems" [popup]="true" appendTo="body"></lfx-menu>
<lfx-menu #actionMenu [model]="committeeActionMenuItems()!" [popup]="true" appendTo="body"></lfx-menu>

<!-- Empty message template -->
<ng-template #emptymessage>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import { Committee } from '@lfx-pcc/shared/interfaces';
import { CommitteeTypeColorPipe } from '@pipes/committee-type-colors.pipe';
import { ProjectService } from '@services/project.service';
import { MenuItem } from 'primeng/api';
import { TooltipModule } from 'primeng/tooltip';

@Component({
selector: 'lfx-committee-table',
standalone: true,
imports: [CommonModule, RouterLink, ButtonComponent, MenuComponent, TableComponent, CommitteeTypeColorPipe],
imports: [CommonModule, RouterLink, ButtonComponent, MenuComponent, TableComponent, CommitteeTypeColorPipe, TooltipModule],
templateUrl: './committee-table.component.html',
})
export class CommitteeTableComponent {
Expand All @@ -32,7 +33,7 @@ export class CommitteeTableComponent {
public selectedCommittee: WritableSignal<Committee | null> = signal(null);

// Menu items for committee actions
public committeeActionMenuItems: MenuItem[] = this.initializeCommitteeActionMenuItems();
public committeeActionMenuItems: Signal<MenuItem[]> = computed(() => this.initializeCommitteeActionMenuItems());

// Organize committees with hierarchy for display
public readonly organizedCommittees: Signal<(Committee & { level?: number })[]> = computed(() => {
Expand Down Expand Up @@ -78,7 +79,7 @@ export class CommitteeTableComponent {
}

private initializeCommitteeActionMenuItems(): MenuItem[] {
return [
const items: MenuItem[] = [
{
label: 'View',
icon: 'fa-light fa-eye',
Expand All @@ -89,20 +90,27 @@ export class CommitteeTableComponent {
}
},
},
{
separator: true,
},
{
label: 'Delete',
icon: 'fa-light fa-trash',
styleClass: 'text-red-500',
command: () => {
const committee = this.selectedCommittee();
if (committee) {
this.onDelete(committee);
}
},
},
];

if (this.selectedCommittee()?.writer) {
items.push(
{
separator: true,
},
{
label: 'Delete',
icon: 'fa-light fa-trash',
styleClass: 'text-red-500',
command: () => {
const committee = this.selectedCommittee();
if (committee) {
this.onDelete(committee);
}
},
}
);
}

return items;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,16 @@ <h3 class="text-sm font-display text-gray-500">Upcoming Meetings</h3>
<i class="fa-light fa-calendar text-4xl text-gray-400 mb-4"></i>
<h3 class="text-lg font-medium text-gray-900 mb-2">No Upcoming Meetings</h3>
<p class="text-gray-600 mb-4 text-sm">There are no upcoming meetings scheduled.</p>
<lfx-button
label="Create Meeting"
icon="fa-light fa-calendar-plus"
severity="secondary"
size="small"
[routerLink]="['meetings/create']"
[relativeTo]="activatedRoute">
</lfx-button>
@if (project()?.writer) {
<lfx-button
label="Create Meeting"
icon="fa-light fa-calendar-plus"
severity="secondary"
size="small"
[routerLink]="['meetings/create']"
[relativeTo]="activatedRoute">
</lfx-button>
}
Comment on lines +202 to +211
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Also gate the empty-state “Add Committee” CTA.

Lines 261–262 remain unconditional. Align with writer gating to avoid exposing actions to read-only users.

- <lfx-button label="Add Committee" icon="fa-light fa-people-group" severity="secondary" size="small" (onClick)="openCreateDialog()">
+ @if (project()?.writer) {
+   <lfx-button label="Add Committee" icon="fa-light fa-people-group" severity="secondary" size="small" (onClick)="openCreateDialog()">
+   </lfx-button>
+ }
- </lfx-button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@if (project()?.writer) {
<lfx-button
label="Create Meeting"
icon="fa-light fa-calendar-plus"
severity="secondary"
size="small"
[routerLink]="['meetings/create']"
[relativeTo]="activatedRoute">
</lfx-button>
}
@if (project()?.writer) {
<lfx-button
label="Add Committee"
icon="fa-light fa-people-group"
severity="secondary"
size="small"
(onClick)="openCreateDialog()">
</lfx-button>
}
🤖 Prompt for AI Agents
In
apps/lfx-pcc/src/app/modules/project/dashboard/project-dashboard/project.component.html
around lines 202–211 (and also apply the same gating to lines 261–262), the
empty-state "Add Committee" CTA is not gated by the project()?.writer check and
therefore is shown to read-only users; wrap the "Add Committee" button/template
in the same @if (project()?.writer) conditional used for the "Create Meeting"
button (or add an *ngIf/project()?.writer equivalent) so the CTA only renders
for writer users, and ensure the relative routing attributes remain unchanged.

</div>
</div>
}
Expand Down
Loading