feat: add video conference meeting link generation to event modal

- Add video conference utility functions for generating random meeting IDs
- Implement video meeting button with camera icon in EventModal
- Add copy and delete functionality with IconButton components
- Support X-OPENPAAS-VIDEOCONFERENCE field in CalendarEvent type
- Handle state management for editing events with/without video conference
- Add meeting link footer to event description
- Include comprehensive test suite for video conference utilities

Features:
- Generate random meeting links (format: xxx-xxxx-xxx)
- Copy meeting link to clipboard
- Remove video conference from events
- Proper state sync when editing different events
- Integration with existing JCal/ICS conversion
This commit is contained in:
lenhanphung
2025-10-02 16:16:04 +07:00
parent 4eb2c86771
commit 298b69b3e1
4 changed files with 215 additions and 0 deletions
@@ -0,0 +1,65 @@
import { generateMeetingId, generateMeetingLink, addVideoConferenceToDescription, extractVideoConferenceFromDescription } from '../videoConferenceUtils';
describe('videoConferenceUtils', () => {
describe('generateMeetingId', () => {
it('should generate meeting ID in correct format', () => {
const meetingId = generateMeetingId();
expect(meetingId).toMatch(/^[a-z]{3}-[a-z]{4}-[a-z]{3}$/);
});
it('should generate different IDs each time', () => {
const id1 = generateMeetingId();
const id2 = generateMeetingId();
expect(id1).not.toBe(id2);
});
});
describe('generateMeetingLink', () => {
it('should generate link with default base URL', () => {
const link = generateMeetingLink();
expect(link).toMatch(/^https:\/\/meet\.linagora\.com\/[a-z]{3}-[a-z]{4}-[a-z]{3}$/);
});
it('should generate link with custom base URL', () => {
const customBase = 'https://custom-meet.example.com';
const link = generateMeetingLink(customBase);
expect(link).toMatch(/^https:\/\/custom-meet\.example\.com\/[a-z]{3}-[a-z]{4}-[a-z]{3}$/);
});
});
describe('addVideoConferenceToDescription', () => {
it('should add video conference footer to empty description', () => {
const description = '';
const meetingLink = 'https://meet.linagora.com/abc-defg-hij';
const result = addVideoConferenceToDescription(description, meetingLink);
expect(result).toBe('\n\nVisio: https://meet.linagora.com/abc-defg-hij');
});
it('should add video conference footer to existing description', () => {
const description = 'This is a meeting description.';
const meetingLink = 'https://meet.linagora.com/abc-defg-hij';
const result = addVideoConferenceToDescription(description, meetingLink);
expect(result).toBe('This is a meeting description.\n\nVisio: https://meet.linagora.com/abc-defg-hij');
});
});
describe('extractVideoConferenceFromDescription', () => {
it('should extract video conference link from description', () => {
const description = 'Meeting description.\n\nVisio: https://meet.linagora.com/abc-defg-hij';
const result = extractVideoConferenceFromDescription(description);
expect(result).toBe('https://meet.linagora.com/abc-defg-hij');
});
it('should return null when no video conference link found', () => {
const description = 'Just a regular meeting description.';
const result = extractVideoConferenceFromDescription(description);
expect(result).toBeNull();
});
it('should return null for empty description', () => {
const description = '';
const result = extractVideoConferenceFromDescription(description);
expect(result).toBeNull();
});
});
});
+48
View File
@@ -0,0 +1,48 @@
/**
* Utility functions for video conference meeting generation
*/
/**
* Generate a random meeting ID in format xxx-xxxx-xxx
* @returns {string} Random meeting ID
*/
export function generateMeetingId(): string {
const chars = 'abcdefghijklmnopqrstuvwxyz';
const generateSegment = (length: number): string => {
return Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
};
return `${generateSegment(3)}-${generateSegment(4)}-${generateSegment(3)}`;
}
/**
* Generate a complete meeting link
* @param {string} baseUrl - Base URL for video conference (from .env.js)
* @returns {string} Complete meeting link
*/
export function generateMeetingLink(baseUrl?: string): string {
const base = baseUrl || (window as any).VIDEO_CONFERENCE_BASE_URL || 'https://meet.linagora.com';
const meetingId = generateMeetingId();
return `${base}/${meetingId}`;
}
/**
* Add video conference footer to event description
* @param {string} description - Original description
* @param {string} meetingLink - Generated meeting link
* @returns {string} Description with video conference footer
*/
export function addVideoConferenceToDescription(description: string, meetingLink: string): string {
const footer = `\n\nVisio: ${meetingLink}`;
return description + footer;
}
/**
* Extract video conference link from description
* @param {string} description - Event description
* @returns {string | null} Video conference link if found, null otherwise
*/
export function extractVideoConferenceFromDescription(description: string): string | null {
const match = description.match(/Visio:\s*(https?:\/\/[^\s]+)/);
return match ? match[1] : null;
}