Step 5: Resources & Summary - To be implemented
diff --git a/apps/lfx-pcc/src/app/modules/project/meetings/components/meeting-create/meeting-create.component.ts b/apps/lfx-pcc/src/app/modules/project/meetings/components/meeting-create/meeting-create.component.ts
index 1a04edaf..3145cfe7 100644
--- a/apps/lfx-pcc/src/app/modules/project/meetings/components/meeting-create/meeting-create.component.ts
+++ b/apps/lfx-pcc/src/app/modules/project/meetings/components/meeting-create/meeting-create.component.ts
@@ -16,12 +16,21 @@ import { MessageService } from 'primeng/api';
import { StepperModule } from 'primeng/stepper';
import { MeetingDetailsComponent } from '../meeting-details/meeting-details.component';
+import { MeetingPlatformFeaturesComponent } from '../meeting-platform-features/meeting-platform-features.component';
import { MeetingTypeSelectionComponent } from '../meeting-type-selection/meeting-type-selection.component';
@Component({
selector: 'lfx-meeting-create',
standalone: true,
- imports: [CommonModule, StepperModule, ButtonComponent, ReactiveFormsModule, MeetingTypeSelectionComponent, MeetingDetailsComponent],
+ imports: [
+ CommonModule,
+ StepperModule,
+ ButtonComponent,
+ ReactiveFormsModule,
+ MeetingTypeSelectionComponent,
+ MeetingDetailsComponent,
+ MeetingPlatformFeaturesComponent,
+ ],
templateUrl: './meeting-create.component.html',
})
export class MeetingCreateComponent {
@@ -33,7 +42,7 @@ export class MeetingCreateComponent {
// Stepper state
public currentStep = signal
(0);
- public readonly totalSteps = 5;
+ public readonly totalSteps = 4;
// Form state
public form = signal(this.createMeetingFormGroup());
@@ -78,6 +87,11 @@ export class MeetingCreateComponent {
public nextStep(): void {
const next = this.currentStep() + 1;
if (next < this.totalSteps && this.canNavigateToStep(next)) {
+ // Auto-generate title when moving from step 1 to step 2
+ if (this.currentStep() === 0 && next === 1) {
+ this.generateMeetingTitle();
+ }
+
this.currentStep.set(next);
this.scrollToStepper();
}
@@ -218,10 +232,7 @@ export class MeetingCreateComponent {
case 2: // Platform & Features
return !!form.get('meetingTool')?.value;
- case 3: // Participants (optional but should not have validation errors)
- return true;
-
- case 4: // Resources & Summary (optional)
+ case 3: // Resources & Summary (optional)
return true;
default:
@@ -253,7 +264,7 @@ export class MeetingCreateComponent {
recurrence: new FormControl('none'),
// Step 3: Platform & Features
- meetingTool: new FormControl('', [Validators.required]),
+ meetingTool: new FormControl('zoom', [Validators.required]),
recording_enabled: new FormControl(false),
transcripts_enabled: new FormControl(false),
youtube_enabled: new FormControl(false),
@@ -459,4 +470,29 @@ export class MeetingCreateComponent {
});
}
}
+
+ private generateMeetingTitle(): void {
+ const form = this.form();
+ const meetingType = form.get('meeting_type')?.value;
+ const startDate = form.get('startDate')?.value;
+
+ // Only auto-generate if we have meeting type and the title is empty
+ const currentTitle = form.get('topic')?.value;
+ if (meetingType && (!currentTitle || currentTitle.trim() === '')) {
+ const formattedDate = startDate
+ ? new Date(startDate).toLocaleDateString('en-US', {
+ month: '2-digit',
+ day: '2-digit',
+ year: 'numeric',
+ })
+ : new Date().toLocaleDateString('en-US', {
+ month: '2-digit',
+ day: '2-digit',
+ year: 'numeric',
+ });
+
+ const generatedTitle = `${meetingType} Meeting - ${formattedDate}`;
+ form.get('topic')?.setValue(generatedTitle);
+ }
+ }
}
diff --git a/apps/lfx-pcc/src/app/modules/project/meetings/components/meeting-details/meeting-details.component.html b/apps/lfx-pcc/src/app/modules/project/meetings/components/meeting-details/meeting-details.component.html
index be4f30db..cf0c62d0 100644
--- a/apps/lfx-pcc/src/app/modules/project/meetings/components/meeting-details/meeting-details.component.html
+++ b/apps/lfx-pcc/src/app/modules/project/meetings/components/meeting-details/meeting-details.component.html
@@ -89,7 +89,8 @@ AI Agenda Generator
id="meeting-agenda"
placeholder="Enter meeting agenda and key discussion points"
[rows]="6"
- styleClass="w-full max-h-40 overflow-y-auto"
+ [autoResize]="true"
+ styleClass="w-full"
data-testid="meeting-details-agenda-textarea">
A clear agenda helps participants prepare and keeps discussions focused
@if (form().get('agenda')?.errors?.['required'] && form().get('agenda')?.touched) {
diff --git a/apps/lfx-pcc/src/app/modules/project/meetings/components/meeting-details/meeting-details.component.ts b/apps/lfx-pcc/src/app/modules/project/meetings/components/meeting-details/meeting-details.component.ts
index dc5c34d8..c0b208d5 100644
--- a/apps/lfx-pcc/src/app/modules/project/meetings/components/meeting-details/meeting-details.component.ts
+++ b/apps/lfx-pcc/src/app/modules/project/meetings/components/meeting-details/meeting-details.component.ts
@@ -107,14 +107,6 @@ export class MeetingDetailsComponent implements OnInit {
this.generateRecurrenceOptions(value as Date);
});
- // Auto-generate title when meeting type and date are available
- this.form()
- .get('startDate')
- ?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef))
- .subscribe(() => {
- this.generateMeetingTitleIfNeeded();
- });
-
// Watch for isRecurring changes to reset recurrence
this.form()
.get('isRecurring')
@@ -201,20 +193,6 @@ export class MeetingDetailsComponent implements OnInit {
return { weekOfMonth, isLastWeek };
}
- // Auto-title generation
- private generateMeetingTitleIfNeeded(): void {
- const meetingType = this.form().get('meeting_type')?.value;
- const startDate = this.form().get('startDate')?.value;
- const currentTitle = this.form().get('topic')?.value;
-
- // Only auto-generate if we have both type and date, and the current title is empty or was auto-generated
- if (meetingType && startDate && (!currentTitle || this.titleWasAutoGenerated())) {
- const newTitle = this.generateMeetingTitle(meetingType, startDate);
- this.form().get('topic')?.setValue(newTitle);
- this.titleWasAutoGenerated.set(true);
- }
- }
-
private generateMeetingTitle(meetingType: string, date: Date): string {
const formattedDate = new Date(date).toLocaleDateString('en-US', {
month: '2-digit',
diff --git a/apps/lfx-pcc/src/app/modules/project/meetings/components/meeting-platform-features/meeting-platform-features.component.html b/apps/lfx-pcc/src/app/modules/project/meetings/components/meeting-platform-features/meeting-platform-features.component.html
new file mode 100644
index 00000000..8c5e5e1b
--- /dev/null
+++ b/apps/lfx-pcc/src/app/modules/project/meetings/components/meeting-platform-features/meeting-platform-features.component.html
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
Meeting Platform & Features
+
Choose your meeting platform and enable helpful features.
+
+
+
+
+
Meeting Platform *
+
+
+ @for (platform of platformOptions; track platform.value) {
+
+ @if (!platform.available) {
+
+ Coming Soon
+
+ }
+
+
+
+
+
+
+
{{ platform.label }}
+
{{ platform.description }}
+
+
+
+ }
+
+
+ @if (form().get('meetingTool')?.errors?.['required'] && form().get('meetingTool')?.touched) {
+
Please select a meeting platform
+ }
+
+
+
+
+
+
+
+
+
+
Meeting Features
+
+ Open source thrives on transparency and accessibility. Recording meetings, generating transcripts, and enabling features helps include community
+ members who can't attend live and creates valuable documentation.
+
+
+
+
+
+
+
+
Meeting Features
+
+ @for (feature of features; track feature.key) {
+
+
+
+
+
+
+
+
+ {{ feature.title }}
+ @if (feature.recommended) {
+ Recommended
+ }
+
+
{{ feature.description }}
+
+
+
+
+
+ }
+
+
+
+ @if (form().get('recording_enabled')?.value) {
+
+
Recording Options
+
+
+
+
Recording Access
+
+
Who can view the meeting recording
+
+
+
+ @if (form().get('zoom_ai_enabled')?.value) {
+
+
AI Summary Access
+
+
Who can access the AI-generated meeting summary
+
+
+
+
+
Require AI Summary Approval
+
AI summary must be reviewed before sharing
+
+
+
+
+ }
+
+ }
+
diff --git a/apps/lfx-pcc/src/app/modules/project/meetings/components/meeting-platform-features/meeting-platform-features.component.ts b/apps/lfx-pcc/src/app/modules/project/meetings/components/meeting-platform-features/meeting-platform-features.component.ts
new file mode 100644
index 00000000..7d3df46d
--- /dev/null
+++ b/apps/lfx-pcc/src/app/modules/project/meetings/components/meeting-platform-features/meeting-platform-features.component.ts
@@ -0,0 +1,38 @@
+// Copyright The Linux Foundation and each contributor to LFX.
+// SPDX-License-Identifier: MIT
+
+import { CommonModule } from '@angular/common';
+import { Component, input } from '@angular/core';
+import { FormGroup, ReactiveFormsModule } from '@angular/forms';
+import { SelectComponent } from '@components/select/select.component';
+import { ToggleComponent } from '@components/toggle/toggle.component';
+import { AI_SUMMARY_ACCESS_OPTIONS, MEETING_FEATURES, MEETING_PLATFORMS, RECORDING_ACCESS_OPTIONS } from '@lfx-pcc/shared/constants';
+import { TooltipModule } from 'primeng/tooltip';
+
+@Component({
+ selector: 'lfx-meeting-platform-features',
+ standalone: true,
+ imports: [CommonModule, ReactiveFormsModule, SelectComponent, ToggleComponent, TooltipModule],
+ templateUrl: './meeting-platform-features.component.html',
+})
+export class MeetingPlatformFeaturesComponent {
+ // Form group input from parent
+ public readonly form = input.required();
+
+ // Constants from shared package
+ public readonly platformOptions = MEETING_PLATFORMS;
+ public readonly features = MEETING_FEATURES;
+ public readonly recordingAccessOptions = RECORDING_ACCESS_OPTIONS;
+ public readonly aiSummaryAccessOptions = AI_SUMMARY_ACCESS_OPTIONS;
+
+ public selectPlatform(platform: string): void {
+ const platformOption = this.platformOptions.find((p) => p.value === platform);
+ if (platformOption?.available) {
+ this.form().get('meetingTool')?.setValue(platform);
+ }
+ }
+
+ public toggleFeature(featureKey: string, enabled: boolean): void {
+ this.form().get(featureKey)?.setValue(enabled);
+ }
+}
diff --git a/packages/shared/src/constants/index.ts b/packages/shared/src/constants/index.ts
index e24f11df..7804a95c 100644
--- a/packages/shared/src/constants/index.ts
+++ b/packages/shared/src/constants/index.ts
@@ -6,5 +6,6 @@ export * from './colors';
export * from './committees';
export * from './file-upload';
export * from './font-sizes';
+export * from './meeting';
export * from './server';
export * from './timezones';
diff --git a/packages/shared/src/constants/meeting.ts b/packages/shared/src/constants/meeting.ts
new file mode 100644
index 00000000..e0129a93
--- /dev/null
+++ b/packages/shared/src/constants/meeting.ts
@@ -0,0 +1,84 @@
+// Copyright The Linux Foundation and each contributor to LFX.
+// SPDX-License-Identifier: MIT
+
+export const MEETING_PLATFORMS = [
+ {
+ value: 'zoom',
+ label: 'Zoom',
+ description: 'Video conferencing with recording and chat features',
+ available: true,
+ icon: 'fa-light fa-video',
+ color: '#0094FF',
+ },
+ {
+ value: 'teams',
+ label: 'Microsoft Teams',
+ description: 'Integrated collaboration with Office 365',
+ available: false,
+ icon: 'fa-light fa-desktop',
+ color: '#6b7280',
+ },
+ {
+ value: 'in-person',
+ label: 'In-Person',
+ description: 'Physical meeting location',
+ available: false,
+ icon: 'fa-light fa-location-dot',
+ color: '#6b7280',
+ },
+];
+
+export const MEETING_FEATURES = [
+ {
+ key: 'recording_enabled',
+ icon: 'fa-light fa-video',
+ title: 'Enable Recording',
+ description: 'Record the meeting for those who cannot attend live',
+ recommended: true,
+ color: '#3b82f6', // blue - matches bg-blue-50 text-blue-700
+ },
+ {
+ key: 'transcripts_enabled',
+ icon: 'fa-light fa-file-lines',
+ title: 'Generate Transcripts',
+ description: 'Automatically create searchable text transcripts',
+ recommended: false,
+ color: '#8b5cf6', // purple - matches bg-purple-50 text-purple-700
+ },
+ {
+ key: 'youtube_enabled',
+ icon: 'fa-light fa-upload',
+ title: 'YouTube Auto-upload',
+ description: "Automatically publish recordings to your project's YouTube channel",
+ recommended: false,
+ color: '#dc2626', // red - matches bg-red-50 text-red-700
+ },
+ {
+ key: 'zoom_ai_enabled',
+ icon: 'fa-light fa-microchip-ai',
+ title: 'AI Meeting Summary',
+ description: 'Generate key takeaways and action items automatically',
+ recommended: true,
+ color: '#16a34a', // green - matches bg-green-50 text-green-700
+ },
+ {
+ key: 'show_in_public_calendar',
+ icon: 'fa-light fa-calendar-check',
+ title: 'Show in Public Calendar',
+ description: 'Make this meeting visible in the public project calendar',
+ recommended: true,
+ color: '#ea580c', // orange - unique color for calendar visibility
+ },
+];
+
+export const RECORDING_ACCESS_OPTIONS = [
+ { label: 'Members Only', value: 'Members' },
+ { label: 'Public', value: 'Public' },
+ { label: 'Private', value: 'Private' },
+];
+
+export const AI_SUMMARY_ACCESS_OPTIONS = [
+ { label: 'PCC Members', value: 'PCC' },
+ { label: 'Project Members', value: 'Members' },
+ { label: 'Public', value: 'Public' },
+];