Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8b2289f
Refactor WaitingRoom component: remove unused viewport hook, replace …
OscarFava Nov 20, 2025
5bdbc8a
Refactor VideoContainer and VignetteEffect components: replace divs w…
OscarFava Nov 21, 2025
4ac29ad
Refactor components to use custom UI elements and improve styling.
OscarFava Nov 21, 2025
0849a4c
Merge remote-tracking branch 'origin/vidsol-271-waiting-room-general'…
OscarFava Nov 21, 2025
35747c0
Refactor background effects terminology and components
OscarFava Nov 24, 2025
938e186
Refactor background effect tests to use updated test IDs and improve …
OscarFava Nov 24, 2025
3aa3dc3
Merge remote-tracking branch 'origin/vidsol-271-waiting-room-general'…
OscarFava Nov 24, 2025
9fcc87b
Refactor imports in AvatarInitials, PreviewAvatar, and VideoContainer…
OscarFava Nov 24, 2025
10fae34
Fix background color logic in CameraButton and MicButton components
OscarFava Nov 24, 2025
6d1917e
Refactor theme colors in WaitingRoom components and update color toke…
OscarFava Nov 24, 2025
68c7702
Refactor LanguageSelector and VividIcon components; add sxToStyle uti…
OscarFava Nov 25, 2025
2cd6af1
Add variant prop to cancel button in BackgroundEffectsLayout for impr…
OscarFava Nov 25, 2025
91074b0
Refactor waiting room tests to improve clarity by specifying button i…
OscarFava Nov 25, 2025
d51dac4
Update visual comparison screenshots
github-actions[bot] Nov 25, 2025
cfb032c
Merge remote-tracking branch 'origin/vidsol-271-waiting-room-general'…
OscarFava Nov 26, 2025
a4fdfb8
Merge remote-tracking branch 'origin/vidsol-271-waiting-room-general'…
OscarFava Dec 2, 2025
2f8aa1b
Update Italian localization strings for various UI components and fea…
OscarFava Dec 2, 2025
5f86d3d
Merge remote-tracking branch 'origin/vidsol-271-waiting-room-general'…
OscarFava Dec 4, 2025
a9c4ea6
Refactor BackgroundEffects components to streamline imports and enhan…
OscarFava Dec 4, 2025
4b913a6
Update visual comparison screenshots
github-actions[bot] Dec 4, 2025
d449ab1
Merge remote-tracking branch 'origin/vidsol-271-waiting-room-general'…
OscarFava Dec 5, 2025
8a33b7c
Update test IDs for microphone and video icons in MicButton tests
OscarFava Dec 5, 2025
d2e8b64
Add data-testid attributes for CameraButton and MicButton components
OscarFava Dec 5, 2025
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,12 @@ This application provides features for common conferencing use cases, such as:
</details>
- <details>
<summary>
Background effects in meeting and waiting room. You can set predefined images, custom image or slight/strong background blur. Images can be uploaded from local device or URL in these formats: JPG, PNG, GIF or BMP. Background effects are not supported in non-Chromium-based browsers or on iOS.
Background settings in meeting and waiting room. You can set predefined images, custom image or slight/strong background blur. Images can be uploaded from local device or URL in these formats: JPG, PNG, GIF or BMP. Background settings are not supported in non-Chromium-based browsers or on iOS.

Please see [OT.hasMediaProcessorSupport](https://vonage.github.io/video-docs/video-js-reference/latest/OT.html#hasMediaProcessorSupport) for more information.
</summary>

<img src="docs/assets/BGEffects.png" alt="Screenshot of background effects">
<img src="docs/assets/BGEffects.png" alt="Screenshot of background settings">
</details>
- <details>
<summary>
Expand Down Expand Up @@ -119,7 +119,7 @@ The Vonage Video API Reference App for React is currently supported on the lates
- ![Safari icon](/docs/assets/safari.svg) Safari
- ![Electron icon](/docs/assets/electron.svg) Electron

*Note:* Some browsers such as Firefox or Safari do not support media processors like video and audio filters (e.g background effects): Please see [OT.hasMediaProcessorSupport](https://vonage.github.io/video-docs/video-js-reference/latest/OT.html#hasMediaProcessorSupport) for more information.
*Note:* Some browsers such as Firefox or Safari do not support media processors like video and audio filters (e.g background settings): Please see [OT.hasMediaProcessorSupport](https://vonage.github.io/video-docs/video-js-reference/latest/OT.html#hasMediaProcessorSupport) for more information.

*Note:* Mobile web views have limited supported at the moment. The minimum supported device width is `360px`.

Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/AvatarInitials/AvatarInitials.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Avatar, SxProps } from '@mui/material';
import { ReactElement } from 'react';
import Avatar from '@ui/Avatar';
import { SxProps } from '@ui/SxProps';
import getParticipantColor from '../../utils/getParticipantColor';

export type InitialsProps = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ vi.mock('react-i18next', () => ({
const translations: Record<string, string> = {
'backgroundEffects.invalidFileType': enTranslations['backgroundEffects.invalidFileType'],
'backgroundEffects.fileTooLarge': enTranslations['backgroundEffects.fileTooLarge'],
'backgroundEffects.linkPlaceholder': enTranslations['backgroundEffects.linkPlaceholder'],
'backgroundEffects.dragDropText': enTranslations['backgroundEffects.dragDropText'],
'backgroundEffects.maxSize': enTranslations['backgroundEffects.maxSize'],
'backgroundEffects.addBackground': enTranslations['backgroundEffects.addBackground'],
};

let translation = translations[key] || key;
Expand Down Expand Up @@ -48,15 +47,18 @@ describe('AddBackgroundEffectLayout', () => {
});

it('should render', () => {
render(<AddBackgroundEffectLayout customBackgroundImageChange={vi.fn()} />);
expect(screen.getByText(/Drag and drop, or click here to upload image/i)).toBeInTheDocument();
expect(screen.getByPlaceholderText(/Link from the web/i)).toBeInTheDocument();
expect(screen.getByTestId('background-effect-link-submit-button')).toBeInTheDocument();
render(
<AddBackgroundEffectLayout backgroundSelected="0" customBackgroundImageChange={vi.fn()} />
);
expect(screen.getByTestId('background-add-background')).toBeInTheDocument();
expect(screen.getByTestId('vivid-icon-gallery-line')).toBeInTheDocument();
});

it('shows error for invalid file type', async () => {
render(<AddBackgroundEffectLayout customBackgroundImageChange={vi.fn()} />);
const input = screen.getByLabelText(/upload/i);
render(
<AddBackgroundEffectLayout backgroundSelected="0" customBackgroundImageChange={vi.fn()} />
);
const input = document.querySelector('input[type="file"]') as HTMLInputElement;
const file = new File(['dummy'], 'test.txt', { type: 'text/plain' });
fireEvent.change(input, { target: { files: [file] } });
expect(
Expand All @@ -65,28 +67,21 @@ describe('AddBackgroundEffectLayout', () => {
});

it('shows error for file size too large', async () => {
render(<AddBackgroundEffectLayout customBackgroundImageChange={vi.fn()} />);
const input = screen.getByLabelText(/upload/i);
render(
<AddBackgroundEffectLayout backgroundSelected="0" customBackgroundImageChange={vi.fn()} />
);
const input = document.querySelector('input[type="file"]') as HTMLInputElement;
const file = new File(['x'.repeat(3 * 1024 * 1024)], 'big.png', { type: 'image/png' });
Object.defineProperty(file, 'size', { value: 3 * 1024 * 1024 });
fireEvent.change(input, { target: { files: [file] } });
expect(await screen.findByText(/Image must be less than 2MB/i)).toBeInTheDocument();
});

it('handles valid image file upload', async () => {
render(<AddBackgroundEffectLayout customBackgroundImageChange={cb} />);
const input = screen.getByLabelText(/upload/i);
render(<AddBackgroundEffectLayout backgroundSelected="0" customBackgroundImageChange={cb} />);
const input = document.querySelector('input[type="file"]') as HTMLInputElement;
const file = new File(['dummy'], 'test.png', { type: 'image/png' });
fireEvent.change(input, { target: { files: [file] } });
await waitFor(() => expect(cb).toHaveBeenCalledWith('data:image/png;base64,MOCKED'));
});

it('handles valid link submit', async () => {
render(<AddBackgroundEffectLayout customBackgroundImageChange={cb} />);
const input = screen.getByPlaceholderText(/Link from the web/i);
fireEvent.change(input, { target: { value: 'https://example.com/image.png' } });
const button = screen.getByTestId('background-effect-link-submit-button');
fireEvent.click(button);
await waitFor(() => expect(cb).toHaveBeenCalledWith('data:image/png;base64,MOCKED_LINK'));
});
});
Original file line number Diff line number Diff line change
@@ -1,43 +1,38 @@
import {
Box,
Button,
CircularProgress,
InputAdornment,
TextField,
Typography,
} from '@mui/material';
import { ChangeEvent, ReactElement, useState } from 'react';
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
import LinkIcon from '@mui/icons-material/Link';
import { ChangeEvent, ReactElement, useState, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import FileUploader from '../../FileUploader/FileUploader';
import Box from '@ui/Box';
import Snackbar from '@ui/Snackbar';
import Alert from '@ui/Alert';
import VividIcon from '@components/VividIcon';
import { ALLOWED_TYPES, MAX_SIZE_MB } from '../../../../utils/constants';
import useImageStorage from '@utils/useImageStorage/useImageStorage';
import SelectableOption from '@components/BackgroundEffects/SelectableOption';

export type AddBackgroundEffectLayoutProps = {
customBackgroundImageChange: (dataUrl: string) => void;
backgroundSelected: string;
};

/**
* AddBackgroundEffectLayout Component
*
* This component manages the UI for adding background effects.
* This component manages the UI for adding background effects via file upload.
* @param {AddBackgroundEffectLayoutProps} props - The props for the component.
* @property {Function} customBackgroundImageChange - Callback function to handle background image change.
* @property {string} backgroundSelected - The currently selected background effect key.
* @returns {ReactElement} The add background effect layout component.
*/
const AddBackgroundEffectLayout = ({
customBackgroundImageChange,
backgroundSelected,
}: AddBackgroundEffectLayoutProps): ReactElement => {
const [fileError, setFileError] = useState<string>('');
const [imageLink, setImageLink] = useState<string>('');
const [linkLoading, setLinkLoading] = useState<boolean>(false);
const { storageError, handleImageFromFile, handleImageFromLink } = useImageStorage();
const [showError, setShowError] = useState<boolean>(false);
const { storageError, handleImageFromFile } = useImageStorage();
const { t } = useTranslation();
const fileInputRef = useRef<HTMLInputElement>(null);

type HandleFileChangeType = ChangeEvent<HTMLInputElement> | { target: { files: FileList } };

const handleFileChange = async (e: HandleFileChangeType) => {
const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => {
const { files } = e.target;
if (!files || files.length === 0) {
return;
Expand All @@ -50,89 +45,68 @@ const AddBackgroundEffectLayout = ({

if (!ALLOWED_TYPES.includes(file.type)) {
setFileError(t('backgroundEffects.invalidFileType'));
setShowError(true);
return;
}

if (file.size > MAX_SIZE_MB * 1024 * 1024) {
setFileError(t('backgroundEffects.fileTooLarge', { maxSize: MAX_SIZE_MB }));
setShowError(true);
return;
}

try {
const newImage = await handleImageFromFile(file);
if (newImage) {
setFileError('');
setShowError(false);
customBackgroundImageChange(newImage.dataUrl);
}
} catch {
setFileError(t('backgroundEffects.processingError'));
setShowError(true);
}
};

const handleLinkSubmit = async () => {
const handleClick = () => {
fileInputRef.current?.click();
};

const handleCloseError = () => {
setFileError('');
setLinkLoading(true);
try {
const newImage = await handleImageFromLink(imageLink);
if (newImage) {
setFileError('');
customBackgroundImageChange(newImage.dataUrl);
} else {
setFileError(t('backgroundEffects.storageError'));
}
} catch {
// error handled in hook
} finally {
setLinkLoading(false);
}
setShowError(false);
};

return (
<Box
sx={{
overflow: 'auto',
}}
>
<FileUploader handleFileChange={handleFileChange} />
const errorMessage = fileError || storageError;

{(fileError || storageError) && (
<Typography color="error" mt={1} fontSize={14}>
{fileError || storageError}
</Typography>
)}
return (
<Box>
<input
ref={fileInputRef}
type="file"
accept={ALLOWED_TYPES.join(',')}
onChange={handleFileChange}
style={{ display: 'none' }}
/>

<Box mt={2} display="flex" alignItems="center" gap={1}>
<TextField
fullWidth
size="small"
placeholder={t('backgroundEffects.linkPlaceholder')}
className="add-background-effect-input"
value={imageLink}
onChange={(e) => setImageLink(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
{linkLoading ? <CircularProgress size={24} /> : <LinkIcon sx={{ fontSize: 24 }} />}
</InputAdornment>
),
}}
/>
<SelectableOption
id="add-background"
title={t('backgroundEffects.addBackground')}
isSelected={backgroundSelected === 'add-background'}
onClick={handleClick}
icon={<VividIcon name="gallery-line" customSize={-2} />}
/>

<Button
data-testid="background-effect-link-submit-button"
variant="contained"
color="primary"
onClick={handleLinkSubmit}
disabled={linkLoading}
style={{ minWidth: 0, padding: '8px 12px' }}
>
{linkLoading ? (
<CircularProgress size={24} color="inherit" />
) : (
<ArrowForwardIcon sx={{ fontSize: 24 }} />
)}
</Button>
</Box>
<Snackbar
open={showError && !!errorMessage}
autoHideDuration={6000}
onClose={handleCloseError}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
>
<Alert onClose={handleCloseError} severity="error" sx={{ width: '100%' }}>
{errorMessage}
</Alert>
</Snackbar>
</Box>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { render, screen } from '@testing-library/react';
import { vi, describe, it, expect } from 'vitest';
import BackgroundEffectOptions from './BackgroundEffectOptions';

describe('BackgroundEffectOptions', () => {
const setBackgroundSelected = vi.fn();
const clearBgWhenSelectedDeleted = vi.fn();
const customBackgroundImageChange = vi.fn();

it('renders background options grid with effects and gallery', () => {
render(
<BackgroundEffectOptions
mode="meeting"
backgroundSelected=""
setBackgroundSelected={setBackgroundSelected}
cleanupSelectedBackgroundReplacement={clearBgWhenSelectedDeleted}
customBackgroundImageChange={customBackgroundImageChange}
/>
);

expect(screen.getByTestId('vivid-icon-remove-line')).toBeInTheDocument();
expect(screen.getByTestId('vivid-icon-blur-line')).toBeInTheDocument();

expect(screen.getByAltText('Bookshelf Room')).toBeInTheDocument();
expect(screen.getByAltText('Busy Room')).toBeInTheDocument();
});
});
Loading
Loading