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 @@ -51,30 +51,30 @@
<div class="flex sm:flex-row flex-col items-start justify-between mb-4" data-testid="meeting-title-section">
<div class="flex-1 pr-4 flex-grow">
<h2 class="text-xl font-display font-semibold text-gray-900 mb-1 leading-tight" data-testid="meeting-title">
{{ meeting().title }}
{{ currentOccurrence()?.title || meeting().title }}
</h2>
</div>
@if (meeting().start_time) {
@if (currentOccurrence()?.start_time || meeting().start_time; as startTime) {
<div class="flex items-center gap-1 text-xs text-gray-600 bg-gray-100 px-2 py-1 rounded flex-shrink-0" data-testid="meeting-datetime">
<i class="fa-light fa-calendar-days text-gray-400"></i>
<span
>{{ meeting()!.start_time | meetingTime: meeting()!.duration : 'date' }} •
{{ meeting()!.start_time | meetingTime: meeting()!.duration : 'time' }}</span
>{{ startTime | meetingTime: currentOccurrence()?.duration || meeting().duration : 'date' }} •
{{ startTime | meetingTime: currentOccurrence()?.duration || meeting().duration : 'time' }}</span
>
</div>
}
</div>

<!-- Meeting Agenda -->
@if (meeting().description) {
@if (currentOccurrence()?.description || meeting().description; as description) {
<div class="mb-4" data-testid="meeting-description">
<div class="flex items-center gap-2 mb-2">
<i class="fa-light fa-file-lines text-gray-400"></i>
<span class="text-sm font-medium text-gray-700 font-sans">Meeting Description</span>
</div>
<lfx-expandable-text [maxHeight]="85">
<div class="text-sm text-gray-600 leading-relaxed bg-gray-50 rounded-lg p-3">
<div [innerHTML]="meeting()!.description | linkify"></div>
<div [innerHTML]="description | linkify"></div>
</div>
</lfx-expandable-text>
</div>
Expand Down Expand Up @@ -134,7 +134,7 @@ <h2 class="text-xl font-display font-semibold text-gray-900 mb-1 leading-tight"
<i class="fa-light fa-clock text-gray-400"></i>
<span>Duration</span>
</div>
<div class="text-sm font-sans">{{ meeting().duration }} minutes</div>
<div class="text-sm font-sans">{{ currentOccurrence()?.duration || meeting().duration }} minutes</div>
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { CardComponent } from '@components/card/card.component';
import { ExpandableTextComponent } from '@components/expandable-text/expandable-text.component';
import { InputTextComponent } from '@components/input-text/input-text.component';
import { environment } from '@environments/environment';
import { extractUrlsWithDomains, Meeting, Project, User } from '@lfx-one/shared';
import { extractUrlsWithDomains, Meeting, MeetingOccurrence, Project, User } from '@lfx-one/shared';
import { MeetingTimePipe } from '@pipes/meeting-time.pipe';
import { MeetingService } from '@services/meeting.service';
import { UserService } from '@services/user.service';
Expand Down Expand Up @@ -57,6 +57,7 @@ export class MeetingJoinComponent {
public joinForm: FormGroup;
public project: WritableSignal<Project | null> = signal<Project | null>(null);
public meeting: Signal<Meeting & { project: Project }>;
public currentOccurrence: Signal<MeetingOccurrence | null>;
public meetingTypeBadge: Signal<{ badgeClass: string; icon?: string; text: string } | null>;
public importantLinks: Signal<{ url: string; domain: string }[]>;
public returnTo: Signal<string | undefined>;
Expand All @@ -72,6 +73,7 @@ export class MeetingJoinComponent {
this.isJoining = signal<boolean>(false);
this.authenticated = this.userService.authenticated;
this.meeting = this.initializeMeeting();
this.currentOccurrence = this.initializeCurrentOccurrence();
this.joinForm = this.initializeJoinForm();
this.formValues = this.initializeFormValues();
this.meetingTypeBadge = this.initializeMeetingTypeBadge();
Expand Down Expand Up @@ -142,6 +144,38 @@ export class MeetingJoinComponent {
) as Signal<Meeting & { project: Project }>;
}

private initializeCurrentOccurrence(): Signal<MeetingOccurrence | null> {
return computed(() => {
const meeting = this.meeting();
if (!meeting?.occurrences || meeting.occurrences.length === 0) {
return null;
}

const now = new Date();
const earlyJoinMinutes = meeting.early_join_time_minutes || 10;

// Find the first occurrence that is currently joinable (within the join window)
const joinableOccurrence = meeting.occurrences.find((occurrence) => {
const startTime = new Date(occurrence.start_time);
const earliestJoinTime = new Date(startTime.getTime() - earlyJoinMinutes * 60000);
const latestJoinTime = new Date(startTime.getTime() + occurrence.duration * 60000 + 40 * 60000); // 40 minutes after end

return now >= earliestJoinTime && now <= latestJoinTime;
});

if (joinableOccurrence) {
return joinableOccurrence;
}

// If no joinable occurrence, find the next future occurrence
const futureOccurrences = meeting.occurrences
.filter((occurrence) => new Date(occurrence.start_time) > now)
.sort((a, b) => new Date(a.start_time).getTime() - new Date(b.start_time).getTime());

return futureOccurrences.length > 0 ? futureOccurrences[0] : null;
});
}

// Private initialization methods
private initializeJoinForm(): FormGroup {
return new FormGroup({
Expand Down Expand Up @@ -197,10 +231,14 @@ export class MeetingJoinComponent {
private initializeImportantLinks(): Signal<{ url: string; domain: string }[]> {
return computed(() => {
const meeting = this.meeting();
if (!meeting?.description) {
const currentOccurrence = this.currentOccurrence();

// Use current occurrence description if available, otherwise fallback to meeting description
const description = currentOccurrence?.description || meeting?.description;
if (!description) {
return [];
}
return extractUrlsWithDomains(meeting.description);
return extractUrlsWithDomains(description);
});
}

Expand All @@ -213,6 +251,20 @@ export class MeetingJoinComponent {
private initializeCanJoinMeeting(): Signal<boolean> {
return computed(() => {
const meeting = this.meeting();
const currentOccurrence = this.currentOccurrence();

// If we have an occurrence, use its timing
if (currentOccurrence) {
const now = new Date();
const startTime = new Date(currentOccurrence.start_time);
const earlyJoinMinutes = meeting.early_join_time_minutes || 10;
const earliestJoinTime = new Date(startTime.getTime() - earlyJoinMinutes * 60000);
const latestJoinTime = new Date(startTime.getTime() + currentOccurrence.duration * 60000 + 40 * 60000); // 40 minutes after end

return now >= earliestJoinTime && now <= latestJoinTime;
}

// Fallback to original meeting logic if no occurrences
if (!meeting?.start_time) {
return false;
}
Expand All @@ -221,8 +273,9 @@ export class MeetingJoinComponent {
const startTime = new Date(meeting.start_time);
const earlyJoinMinutes = meeting.early_join_time_minutes || 10; // Default to 10 minutes
const earliestJoinTime = new Date(startTime.getTime() - earlyJoinMinutes * 60000);
const latestJoinTime = new Date(startTime.getTime() + meeting.duration * 60000 + 40 * 60000); // 40 minutes after end

return now >= earliestJoinTime;
return now >= earliestJoinTime && now <= latestJoinTime;
});
}

Expand Down