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
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ <h4 class="line-clamp-2 text-sm leading-tight font-medium text-gray-900" data-te
<lfx-button
class="w-full"
label="See Meeting Details"
[routerLink]="['/meetings', meeting().uid]"
[queryParams]="{ password: meeting().password || '' }"
[routerLink]="meetingDetailRouterLink()"
[queryParams]="meetingDetailQueryParams()"
target="_blank"
type="button"
rel="noopener noreferrer"
Expand Down Expand Up @@ -130,11 +130,9 @@ <h4 class="line-clamp-2 text-sm leading-tight font-medium text-gray-900" data-te
size="small"
class="w-full"
label="Join Meeting"
[routerLink]="['/meetings', meeting().uid]"
[queryParams]="{ password: meeting().password || '' }"
[routerLink]="meetingDetailRouterLink()"
[queryParams]="meetingDetailQueryParams()"
target="_blank"
type="button"
type="button"
rel="noopener noreferrer"
icon="fa-light fa-video"
styleClass="w-full bg-emerald-500 hover:bg-emerald-600 text-white h-8 text-sm font-semibold"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,22 +140,53 @@ export class DashboardMeetingCardComponent {
return this.meeting().transcript_enabled === true;
});

// TODO(v1-migration): Simplify to use V2 fields only once all meetings are migrated to V2
public readonly hasAiSummary: Signal<boolean> = computed(() => {
return this.meeting().zoom_config?.ai_companion_enabled === true;
const meeting = this.meeting();
// V2: zoom_config.ai_companion_enabled, V1: zoom_ai_enabled
return meeting.zoom_config?.ai_companion_enabled === true || meeting.zoom_ai_enabled === true;
});

// TODO(v1-migration): Simplify to use V2 fields only once all meetings are migrated to V2
public readonly meetingTitle: Signal<string> = computed(() => {
const occurrence = this.occurrence();
const meeting = this.meeting();

// Use occurrence title if available, otherwise use meeting title
return occurrence?.title || meeting.title;
// Priority: occurrence title > meeting title > meeting topic (v1)
return occurrence?.title || meeting.title || meeting.topic || '';
});

public readonly canJoinMeeting: Signal<boolean> = computed(() => {
return canJoinMeeting(this.meeting(), this.occurrence());
});

// TODO(v1-migration): Remove once all meetings are migrated to V2
public readonly isLegacyMeeting: Signal<boolean> = computed(() => {
return this.meeting().version === 'v1';
});

// TODO(v1-migration): Simplify to use V2 uid only once all meetings are migrated to V2
public readonly meetingDetailRouterLink: Signal<string[]> = computed(() => {
const meeting = this.meeting();
const identifier = this.isLegacyMeeting() && meeting.id ? meeting.id : meeting.uid;
return ['/meetings', identifier];
});

// TODO(v1-migration): Remove V1 parameter handling once all meetings are migrated to V2
public readonly meetingDetailQueryParams: Signal<Record<string, string>> = computed(() => {
const meeting = this.meeting();
const params: Record<string, string> = {};

if (meeting.password) {
params['password'] = meeting.password;
}
if (this.isLegacyMeeting()) {
params['v1'] = 'true';
}

return params;
});

public constructor() {
// Convert meeting input signal to observable and create reactive attachment stream
const meeting$ = toObservable(this.meeting);
Expand All @@ -170,13 +201,20 @@ export class DashboardMeetingCardComponent {

this.attachments = toSignal(attachments$, { initialValue: [] });

// TODO(v1-migration): Remove V1 join URL handling once all meetings are migrated to V2
// Convert user signal to observable and create reactive join URL stream
const user$ = toObservable(this.userService.user);
const authenticated$ = toObservable(this.userService.authenticated);
const isLegacyMeeting$ = toObservable(this.isLegacyMeeting);

const joinUrl$ = combineLatest([meeting$, user$, authenticated$, isLegacyMeeting$]).pipe(
switchMap(([meeting, user, authenticated, isLegacy]) => {
// For v1 meetings, use the join_url directly from the meeting object
if (isLegacy && meeting.join_url && this.canJoinMeeting()) {
return of(meeting.join_url);
}

const joinUrl$ = combineLatest([meeting$, user$, authenticated$]).pipe(
switchMap(([meeting, user, authenticated]) => {
// Only fetch join URL for meetings that can be joined with authenticated users
// For v2 meetings, fetch join URL from API for authenticated users
if (meeting.uid && authenticated && user?.email && this.canJoinMeeting()) {
return this.meetingService.getPublicMeetingJoinUrl(meeting.uid, meeting.password, { email: user.email }).pipe(
map((res) => buildJoinUrlWithParams(res.join_url, user)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ <h2 class="py-1 flex items-center gap-2">
<div class="flex flex-col gap-3" data-testid="dashboard-pending-actions-list">
@for (item of pendingActions(); track $index) {
<div
class="p-4 border rounded-lg shadow-md"
class="p-4 border rounded-lg shadow-md space-y-3"
[ngClass]="{
'bg-yellow-50 hover:border-yellow-300': item.color === 'amber',
'bg-blue-200 hover:border-blue-300': item.color === 'blue',
Expand All @@ -25,7 +25,7 @@ <h2 class="py-1 flex items-center gap-2">
}"
[attr.data-testid]="'dashboard-pending-actions-item-' + item.type">
<!-- Header with Type -->
<div class="flex items-start justify-between mb-3">
<div class="flex items-center gap-2 flex-wrap">
<div
class="flex items-center gap-2 px-2 py-1 rounded-md text-xs"
[ngClass]="{
Expand Down Expand Up @@ -57,36 +57,45 @@ <h2 class="py-1 flex items-center gap-2">
</div>

<!-- Action Text -->
<div class="mb-4">
<p class="text-base font-normal">
{{ item.text }}
</p>
<div class="flex items-start justify-between gap-2">
<div class="flex flex-col gap-1">
<p class="text-sm leading-tight font-medium text-gray-900">
{{ item.text }}
</p>
@if (item.date) {
<div class="flex items-center gap-2 flex-wrap mt-0.5">
<span class="text-xs text-gray-600 whitespace-nowrap">{{ item.date }}</span>
</div>
}
</div>
</div>

<!-- Action Button -->
@if (item.buttonLink) {
<lfx-button
size="small"
class="w-full"
styleClass="w-full text-sm"
severity="secondary"
rel="noopener noreferrer"
[link]="true"
[href]="item.buttonLink"
target="_blank"
(onClick)="handleActionClick(item)"
[label]="item.buttonText">
</lfx-button>
} @else {
<lfx-button
size="small"
class="w-full text-sm"
styleClass="w-full text-sm"
severity="secondary"
(onClick)="handleActionClick(item)"
[label]="item.buttonText">
</lfx-button>
}
<div class="flex gap-2">
@if (item.buttonLink) {
<lfx-button
size="small"
class="w-full"
styleClass="w-full text-sm"
severity="secondary"
rel="noopener noreferrer"
[link]="true"
[href]="item.buttonLink"
target="_blank"
(onClick)="handleActionClick(item)"
[label]="item.buttonText">
</lfx-button>
} @else {
<lfx-button
size="small"
class="w-full text-sm"
styleClass="w-full text-sm"
severity="secondary"
(onClick)="handleActionClick(item)"
[label]="item.buttonText">
</lfx-button>
}
</div>
</div>
}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
data-testid="share-meeting-button"
tooltip="Share Meeting"></lfx-button>
}
@if (meeting().project_slug && meeting().organizer && !pastMeeting()) {
@if (meeting().project_slug && meeting().organizer && !pastMeeting() && !isLegacyMeeting()) {
<lfx-button
icon="fa-light fa-edit"
[text]="true"
Expand All @@ -73,7 +73,7 @@
meeting().committees && meeting().committees!.length > 0 ? 'Manage ' + committeeLabel.plural : 'Connect ' + committeeLabel.plural
"></lfx-button>
}
@if (meeting().organizer) {
@if (meeting().organizer && !isLegacyMeeting()) {
<lfx-button
icon="fa-light fa-trash"
[text]="true"
Expand All @@ -89,9 +89,9 @@

<!-- Meeting Title -->
<div class="flex items-center gap-2 mb-2" data-testid="meeting-title-section">
@if (occurrence()?.title || meeting().title) {
@if (meetingTitle()) {
<h3 class="text-base font-medium text-gray-900 leading-tight tracking-tight" data-testid="meeting-title">
{{ occurrence()?.title || meeting().title }}
{{ meetingTitle() }}
</h3>
}
</div>
Expand All @@ -114,24 +114,24 @@ <h3 class="text-base font-medium text-gray-900 leading-tight tracking-tight" dat
@if (meeting().transcript_enabled) {
<lfx-tag value="Transcripts" severity="secondary" icon="fa-light fa-file-lines"></lfx-tag>
}
@if (meeting().zoom_config?.ai_companion_enabled) {
@if (hasAiCompanion()) {
<lfx-tag value="AI Summary" severity="success" icon="fa-light fa-sparkles"></lfx-tag>
}
</div>

<!-- Description Section -->
@if (occurrence()?.description || meeting().description) {
@if (meetingDescription()) {
<div class="my-3" data-testid="meeting-description">
<lfx-expandable-text [maxHeight]="85">
<div class="text-gray-600 text-[12.25px] leading-relaxed tracking-tight">
<div [innerHTML]="occurrence()?.description || meeting().description | linkify"></div>
<div [innerHTML]="meetingDescription() | linkify"></div>
</div>
</lfx-expandable-text>
</div>
}

<!-- AI Summary Review Banner for Organizers -->
@if (pastMeeting() && meeting().organizer && hasSummary() && !summaryApproved()) {
@if (pastMeeting() && meeting().organizer && !isLegacyMeeting() && hasSummary() && !summaryApproved()) {
<div class="flex items-center gap-2.5 px-3.5 py-3 bg-blue-50 border border-blue-200 rounded-md mb-3">
<i class="fa-light fa-sparkles text-base text-blue-600 flex-shrink-0 mt-0.5"></i>
<div class="flex-1 flex items-center justify-between gap-3">
Expand Down Expand Up @@ -227,7 +227,7 @@ <h3 class="text-base font-medium text-gray-900 leading-tight tracking-tight" dat
}
</div>
}
@if (pastMeeting() && meeting().organizer) {
@if (pastMeeting() && meeting().organizer && !isLegacyMeeting()) {
<div class="flex flex-col md:flex-row gap-2 mt-3.5">
@if (hasRecording()) {
<lfx-button
Expand Down Expand Up @@ -259,18 +259,25 @@ <h3 class="text-base font-medium text-gray-900 leading-tight tracking-tight" dat

<!-- People/Attendees Card Column -->
<div class="flex flex-col">
@if (meeting().organizer) {
@if (meeting().organizer && !isLegacyMeeting()) {
<!-- Show RSVP Details for organizers and past meetings -->
<lfx-meeting-rsvp-details
[meeting]="meeting()"
[currentOccurrence]="currentOccurrence()"
[pastMeeting]="pastMeeting()"
[showAddButton]="!pastMeeting()"
[additionalRegistrantsCount]="additionalRegistrantsCount()">
[additionalRegistrantsCount]="additionalRegistrantsCount()"
[disabled]="isLegacyMeeting()"
disabledMessage="Meetings created outside of LFX One do not have RSVP functionality">
</lfx-meeting-rsvp-details>
} @else if (!pastMeeting()) {
<!-- Show RSVP Selection for authenticated non-organizers (upcoming meetings only) -->
<lfx-rsvp-button-group [meeting]="meeting()" [occurrenceId]="occurrence()?.occurrence_id"> </lfx-rsvp-button-group>
<lfx-rsvp-button-group
[meeting]="meeting()"
[occurrenceId]="occurrence()?.occurrence_id"
[disabled]="isLegacyMeeting()"
disabledMessage="Meetings created outside of LFX One do not have RSVP functionality">
</lfx-rsvp-button-group>
} @else if (pastMeeting() && !meeting().organizer) {
<!-- Show Recording and AI Summary buttons for past meetings (non-organizers) -->
<div class="flex flex-col gap-2">
Expand Down Expand Up @@ -303,7 +310,7 @@ <h3 class="text-base font-medium text-gray-900 leading-tight tracking-tight" dat

<!-- Action Buttons for People Column -->
<div class="flex gap-2" [ngClass]="{ 'mt-3.5': !pastMeeting() || meeting().organizer }">
@if (meeting().organizer) {
@if (meeting().organizer && !isLegacyMeeting()) {
<lfx-button
class="w-full"
icon="fa-light fa-users"
Expand All @@ -324,22 +331,24 @@ <h3 class="text-base font-medium text-gray-900 leading-tight tracking-tight" dat
severity="secondary"
styleClass="w-full"
data-testid="see-meeting-details-button"
[href]="'/meetings/' + meeting().uid + '?password=' + meeting().password"
[href]="meetingDetailUrl()"
target="_blank">
</lfx-button>
}
</div>
</div>
</div>

<!-- Meeting Registrants -->
<lfx-meeting-registrants-display
[meeting]="meeting()"
[pastMeeting]="pastMeeting()"
[visible]="showRegistrants()"
[showAddRegistrant]="!pastMeeting()"
(registrantsCountChange)="additionalRegistrantsCount.set($event)">
</lfx-meeting-registrants-display>
<!-- Meeting Registrants (v2 meetings only) -->
@if (!isLegacyMeeting()) {
<lfx-meeting-registrants-display
[meeting]="meeting()"
[pastMeeting]="pastMeeting()"
[visible]="showRegistrants()"
[showAddRegistrant]="!pastMeeting()"
(registrantsCountChange)="additionalRegistrantsCount.set($event)">
</lfx-meeting-registrants-display>
}

<!-- Confirmation Dialog -->
<p-confirmDialog></p-confirmDialog>
Expand Down
Loading