Skip to content
71 changes: 47 additions & 24 deletions frontend/src/components/GoodBye/ArchiveList/ArchiveList.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
import WarningOutlinedIcon from '@mui/icons-material/WarningOutlined';
import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';
import {
CircularProgress,
IconButton,
Link,
List,
ListItem,
ListItemText,
Tooltip,
} from '@mui/material';
import { ReactElement } from 'react';
import { useTranslation } from 'react-i18next';
import Link from '@ui/Link';
import Tooltip from '@ui/Tooltip';
import IconButton from '@ui/IconButton';
import List from '@ui/List';
import ListItem from '@ui/ListItem';
import CircularProgress from '@ui/CircularProgress';
import ListItemText from '@ui/ListItemText';
import Box from '@ui/Box';
import Typography from '@ui/Typography';
import useMediaQuery from '@ui/useMediaQuery';
import useCustomTheme from '@Context/Theme';
import VividIcon from '@components/VividIcon';
import { Archive, ArchiveStatus } from '../../../api/archiving/model';

const ArchiveDownloadButton = ({ url, id }: { id: string; url: string | undefined }) => {
const { t } = useTranslation();
const theme = useCustomTheme();

return (
<Link href={url} target="_blank">
<Tooltip title={t('archiveList.download.tooltip', { id })}>
<IconButton>
<FileDownloadOutlinedIcon data-testid="archive-download-button" />
<VividIcon
name="download-line"
customSize={-4}
data-testid="archive-download-button"
sx={{ color: theme.colors.secondary }}
/>
</IconButton>
</Tooltip>
</Link>
Expand All @@ -29,20 +36,23 @@ const ArchiveDownloadButton = ({ url, id }: { id: string; url: string | undefine

const ArchiveErrorIcon = () => {
const { t } = useTranslation();
const theme = useCustomTheme();

return (
<Tooltip title={t('archiveList.error.tooltip')}>
<WarningOutlinedIcon
color="warning"
<VividIcon
name="warning-line"
customSize={-3}
data-testid="archive-error-icon"
sx={{
color: theme.colors.warning,
alignItems: 'center',
display: 'flex',
width: '40px',
height: '40px',
padding: '8px',
padding: 1,
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Inconsistent padding unit usage. Line 44 uses spacing unit 1 while line 60 uses shorthand p: 1. For consistency and maintainability, use the same pattern throughout the file. Consider using the longhand padding property on both lines or the shorthand p on both lines.

Suggested change
padding: 1,
p: 1,

Copilot uses AI. Check for mistakes.
justifyContent: 'center',
}}
data-testid="archive-error-icon"
/>
</Tooltip>
);
Expand All @@ -55,7 +65,7 @@ const ArchivingLoadingIcon = () => {
<CircularProgress
data-testid="archive-loading-spinner"
sx={{
padding: '8px',
p: 1,
}}
/>
</Tooltip>
Expand Down Expand Up @@ -94,20 +104,33 @@ export type ArchiveListProps = {
*/
const ArchiveList = ({ archives }: ArchiveListProps): ReactElement => {
const { t } = useTranslation();
const isMdUp = useMediaQuery('(min-width:768px)');
const theme = useCustomTheme();

if (archives === 'error') {
return (
<>
<WarningOutlinedIcon color="warning" />
<h3 className="text-lg text-slate-500">{t('archiveList.error.text')}</h3>
</>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<VividIcon name="warning-line" customSize={-4} sx={{ color: theme.colors.warning }} />
<Typography variant="h6" sx={{ color: theme.colors.textTertiary }}>
{t('archiveList.error.text')}
</Typography>
</Box>
);
}
if (!archives.length) {
return <h3 className="text-lg text-slate-500">{t('archiveList.empty')}</h3>;
return (
<Typography variant="h6" sx={{ color: theme.colors.textTertiary }}>
{t('archiveList.empty')}
</Typography>
);
}
return (
<div className="md:max-h-[480px] md:overflow-y-auto ">
<Box
sx={{
maxHeight: isMdUp ? '480px' : 'none',
overflowY: isMdUp ? 'auto' : 'visible',
}}
>
<List sx={{ overflowX: 'auto' }}>
{archives.map((archive, index) => {
return (
Expand All @@ -128,7 +151,7 @@ const ArchiveList = ({ archives }: ArchiveListProps): ReactElement => {
);
})}
</List>
</div>
</Box>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe('GoToLandingPageButton', () => {
const button = screen.getByTestId('go-to-landing-button');
await userEvent.click(button);

expect(screen.getByText('Return to landing page')).toBeInTheDocument();
expect(screen.getByText('View Landing Page')).toBeInTheDocument();
expect(mockFn).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button } from '@mui/material';
import Button from '@ui/Button';
import { MouseEvent, ReactElement, TouchEvent } from 'react';
import { useTranslation } from 'react-i18next';

Expand All @@ -18,13 +18,7 @@ const GoToLandingPageButton = ({ handleLanding }: GoToLandingPageButtonProps): R
const { t } = useTranslation();

return (
<Button
data-testid="go-to-landing-button"
variant="contained"
className="h-12"
sx={{ textTransform: 'none', fontSize: '1rem', marginBottom: '16px' }}
onClick={handleLanding}
>
<Button data-testid="go-to-landing-button" variant="outlined" onClick={handleLanding} fullWidth>
{t('goodBye.back')}
</Button>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { render, screen } from '@testing-library/react';
import { MemoryRouter, useNavigate } from 'react-router-dom';
import { describe, expect, it, Mock, vi, beforeEach, afterAll } from 'vitest';
import userEvent from '@testing-library/user-event';
import GoodByeMessage from './GoodbyeMessage';
import { SMALL_VIEWPORT } from '../../../utils/constants';

vi.mock('react-router-dom', async () => {
const mod = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
Expand All @@ -13,15 +11,6 @@ vi.mock('react-router-dom', async () => {
const mockNavigate = vi.fn();
const headerMessage = 'This is a header message';
const goodbyeMessage = 'This is a goodbye message';
const roomName = 'This is a test room';
const matchMediaCommon = {
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
};

describe('GoodbyeMessage', () => {
const originalMatchMedia = globalThis.matchMedia;
Expand All @@ -37,7 +26,7 @@ describe('GoodbyeMessage', () => {
it('renders the header', () => {
render(
<MemoryRouter>
<GoodByeMessage roomName={roomName} message={goodbyeMessage} header={headerMessage} />
<GoodByeMessage message={goodbyeMessage} header={headerMessage} />
</MemoryRouter>
);
const header = screen.getByTestId('header-message');
Expand All @@ -47,59 +36,10 @@ describe('GoodbyeMessage', () => {
it('renders the goodbye message', () => {
render(
<MemoryRouter>
<GoodByeMessage roomName={roomName} message={goodbyeMessage} header={headerMessage} />
<GoodByeMessage message={goodbyeMessage} header={headerMessage} />
</MemoryRouter>
);
const goodbye = screen.getByTestId('goodbye-message');
expect(goodbye.textContent).toBe(goodbyeMessage);
});

it('renders the re-enter button and navigates back to the waiting room', async () => {
render(
<MemoryRouter>
<GoodByeMessage roomName={roomName} message={goodbyeMessage} header={headerMessage} />
</MemoryRouter>
);
const reenterButton = screen.getByTestId('reenterButton');
expect(reenterButton).toBeInTheDocument();

await userEvent.click(reenterButton);
expect(mockNavigate).toHaveBeenCalledWith(`/waiting-room/${roomName}`);
});

it('renders correctly on screen less than SMALL_VIEWPORT', () => {
globalThis.matchMedia = vi.fn((query: string) => ({
matches: new RegExp(`\\(max-width:\\s*${SMALL_VIEWPORT}px\\)`).test(query),
media: query,
...matchMediaCommon,
}));

render(
<MemoryRouter>
<GoodByeMessage roomName={roomName} message={goodbyeMessage} header={headerMessage} />
</MemoryRouter>
);

expect(screen.getByTestId('goodbye-message')).toBeInTheDocument();
expect(screen.getByTestId('goodbye-message')).toHaveClass('w-full');
expect(screen.getByTestId('goodbye-message')).not.toHaveClass('w-[400px]');
});

it('renders correctly on screen greater than SMALL_VIEWPORT', () => {
globalThis.matchMedia = vi.fn((query: string) => ({
matches: new RegExp(`\\(min-width:\\s*${SMALL_VIEWPORT + 1}px\\)`).test(query),
media: query,
...matchMediaCommon,
}));

render(
<MemoryRouter>
<GoodByeMessage roomName={roomName} message={goodbyeMessage} header={headerMessage} />
</MemoryRouter>
);

expect(screen.getByTestId('goodbye-message')).toBeInTheDocument();
expect(screen.getByTestId('goodbye-message')).toHaveClass('w-[400px]');
expect(screen.getByTestId('goodbye-message')).not.toHaveClass('w-full');
});
});
60 changes: 34 additions & 26 deletions frontend/src/components/GoodBye/GoodbyeMessage/GoodbyeMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { useNavigate } from 'react-router-dom';
import { ReactElement } from 'react';
import GoToLandingPageButton from '../GoToLandingPageButton';
import ReenterRoomButton from '../ReenterRoomButton';
import useIsSmallViewport from '../../../hooks/useIsSmallViewport';
import Box from '@ui/Box';
import Typography from '@ui/Typography';
import useCustomTheme from '@Context/Theme';

export type GoodByeMessageProps = {
header: string;
message: string;
roomName: string;
};

/**
Expand All @@ -16,33 +14,43 @@ export type GoodByeMessageProps = {
* @param {GoodByeMessageProps} props - The props for the component.
* @returns {ReactElement} The GoodByeMessage component.
*/
const GoodByeMessage = ({ header, message, roomName }: GoodByeMessageProps): ReactElement => {
const isSmallViewport = useIsSmallViewport();
const navigate = useNavigate();
const handleLanding = () => {
navigate('/');
};
const GoodByeMessage = ({ header, message }: GoodByeMessageProps): ReactElement => {
const theme = useCustomTheme();

const handleReenter = () => {
navigate(`/waiting-room/${roomName}`);
};
return (
<div className="h-auto w-full shrink py-4 ps-12 text-left">
<h2 className="w-9/12 pb-5 text-5xl text-black" data-testid="header-message">
<Box
sx={{
height: 'auto',
width: '100%',
flexShrink: 1,
paddingTop: 2,
paddingBottom: 2,
paddingLeft: { xs: 2, md: 6 },
textAlign: 'left',
}}
>
<Typography
variant="h2"
sx={{
width: { xs: '100%', md: '80%' },
paddingBottom: 2,
color: theme.colors.textSecondary,
}}
data-testid="header-message"
>
{header}
</h2>
<h3
className={`pr-12 text-lg text-slate-500 ${isSmallViewport ? 'w-full' : 'w-[400px]'}`}
</Typography>
<Typography
variant="h4"
sx={{
color: theme.colors.textTertiary,
display: { xs: 'none', sm: 'block' },
}}
data-testid="goodbye-message"
>
{message}
</h3>
<div className="mt-6 flex flex-row items-center pr-0">
<ReenterRoomButton handleReenter={handleReenter} roomName={roomName} />

<GoToLandingPageButton handleLanding={handleLanding} />
</div>
</div>
</Typography>
</Box>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe('ReenterRoomButton', () => {
const button = screen.getByTestId('reenterButton');
await userEvent.click(button);

expect(screen.getByText('Re-enter')).toBeInTheDocument();
expect(screen.getByText('Go back to meeting')).toBeInTheDocument();
expect(mockFn).toHaveBeenCalled();
});

Expand All @@ -22,7 +22,7 @@ describe('ReenterRoomButton', () => {
render(<ReenterRoomButton roomName="" handleReenter={mockFn} />);

expect(screen.queryByTestId('reenterButton')).not.toBeInTheDocument();
expect(screen.queryByText('Re-enter')).not.toBeInTheDocument();
expect(screen.queryByText('Go back to meeting')).not.toBeInTheDocument();
expect(mockFn).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Button } from '@mui/material';
import VividIcon from '@components/VividIcon';
import Button from '@ui/Button';
import { MouseEvent, ReactElement, TouchEvent } from 'react';
import { useTranslation } from 'react-i18next';

Expand All @@ -25,16 +26,12 @@ const ReenterRoomButton = ({
return (
roomName && (
<Button
variant="outlined"
className="h-12"
sx={{ mb: 3 }}
startIcon={<VividIcon name="enter-line" customSize={-5} />}
variant="contained"
data-testid="reenterButton"
sx={{
textTransform: 'none',
fontSize: '1rem',
marginRight: '8px',
marginBottom: '16px',
}}
onClick={handleReenter}
fullWidth
>
{t('goodBye.reEnter')}
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ vi.mock('@vonage/client-sdk-video', () => ({

describe('RedirectToUnsupportedBrowserPage', () => {
const supportedText = 'You have arrived';
const unsupportedText = 'Your browser is unsupported';
const unsupportedText = 'Your browser is not compatible.';
const TestComponent = () => <div>{supportedText}</div>;

afterEach(() => {
Expand Down
Loading