583 lines
19 KiB
TypeScript
583 lines
19 KiB
TypeScript
import React, { useMemo } from 'react';
|
|
import ModalFullscreen from '/imports/ui/components/common/modal/fullscreen/component';
|
|
import { defineMessages, useIntl } from 'react-intl';
|
|
import { range } from 'ramda';
|
|
import { uniqueId } from '/imports/utils/string-utils';
|
|
import { useIsImportPresentationWithAnnotationsFromBreakoutRoomsEnabled, useIsImportSharedNotesFromBreakoutRoomsEnabled } from '/imports/ui/services/features';
|
|
import useMeeting from '/imports/ui/core/hooks/useMeeting';
|
|
import { useLazyQuery, useQuery, useMutation } from '@apollo/client';
|
|
import Styled from './styles';
|
|
import {
|
|
getBreakouts,
|
|
getBreakoutsResponse,
|
|
getUser,
|
|
getUserResponse,
|
|
} from './queries';
|
|
import logger from '/imports/startup/client/logger';
|
|
import BreakoutRoomUserAssignment from './breakout-room-user-assignment/component';
|
|
import deviceInfo from '/imports/utils/deviceInfo';
|
|
import BreakoutRoomUserAssignmentMobile from './breakout-room-user-assignment-mobile/component';
|
|
import RoomManagmentState from './room-managment-state/component';
|
|
import {
|
|
Rooms,
|
|
RoomToWithSettings,
|
|
BreakoutUser,
|
|
moveUserRegistery,
|
|
} from './room-managment-state/types';
|
|
import { BREAKOUT_ROOM_CREATE, BREAKOUT_ROOM_MOVE_USER } from '../mutations';
|
|
|
|
const MIN_BREAKOUT_ROOMS = 2;
|
|
const MIN_BREAKOUT_TIME = 5;
|
|
const DEFAULT_BREAKOUT_TIME = 15;
|
|
|
|
interface CreateBreakoutRoomContainerProps {
|
|
isOpen: boolean
|
|
setIsOpen: (isOpen: boolean) => void
|
|
priority: string,
|
|
isUpdate?: boolean,
|
|
}
|
|
|
|
interface CreateBreakoutRoomProps extends CreateBreakoutRoomContainerProps {
|
|
isBreakoutRecordable: boolean,
|
|
users: Array<BreakoutUser>,
|
|
runningRooms: getBreakoutsResponse['breakoutRoom'],
|
|
}
|
|
|
|
const intlMessages = defineMessages({
|
|
breakoutRoomTitle: {
|
|
id: 'app.createBreakoutRoom.title',
|
|
description: 'modal title',
|
|
},
|
|
breakoutRoomDesc: {
|
|
id: 'app.createBreakoutRoom.modalDesc',
|
|
description: 'modal description',
|
|
},
|
|
breakoutRoomUpdateDesc: {
|
|
id: 'app.updateBreakoutRoom.modalDesc',
|
|
description: 'update modal description',
|
|
},
|
|
cancelLabel: {
|
|
id: 'app.updateBreakoutRoom.cancelLabel',
|
|
description: 'used in the button that close update modal',
|
|
},
|
|
updateTitle: {
|
|
id: 'app.updateBreakoutRoom.title',
|
|
description: 'update breakout title',
|
|
},
|
|
updateConfirm: {
|
|
id: 'app.updateBreakoutRoom.confirm',
|
|
description: 'Update to breakout confirm button label',
|
|
},
|
|
resetUserRoom: {
|
|
id: 'app.update.resetRoom',
|
|
description: 'Reset user room button label',
|
|
},
|
|
confirmButton: {
|
|
id: 'app.createBreakoutRoom.confirm',
|
|
description: 'confirm button label',
|
|
},
|
|
dismissLabel: {
|
|
id: 'app.presentationUploder.dismissLabel',
|
|
description: 'used in the button that close modal',
|
|
},
|
|
numberOfRooms: {
|
|
id: 'app.createBreakoutRoom.numberOfRooms',
|
|
description: 'number of rooms label',
|
|
},
|
|
duration: {
|
|
id: 'app.createBreakoutRoom.durationInMinutes',
|
|
description: 'duration time label',
|
|
},
|
|
resetAssignments: {
|
|
id: 'app.createBreakoutRoom.resetAssignments',
|
|
description: 'reset assignments label',
|
|
},
|
|
resetAssignmentsDesc: {
|
|
id: 'app.createBreakoutRoom.resetAssignmentsDesc',
|
|
description: 'reset assignments label description',
|
|
},
|
|
randomlyAssign: {
|
|
id: 'app.createBreakoutRoom.randomlyAssign',
|
|
description: 'randomly assign label',
|
|
},
|
|
randomlyAssignDesc: {
|
|
id: 'app.createBreakoutRoom.randomlyAssignDesc',
|
|
description: 'randomly assign label description',
|
|
},
|
|
breakoutRoom: {
|
|
id: 'app.createBreakoutRoom.room',
|
|
description: 'breakout room',
|
|
},
|
|
freeJoinLabel: {
|
|
id: 'app.createBreakoutRoom.freeJoin',
|
|
description: 'free join label',
|
|
},
|
|
captureNotesLabel: {
|
|
id: 'app.createBreakoutRoom.captureNotes',
|
|
description: 'capture shared notes label',
|
|
},
|
|
captureSlidesLabel: {
|
|
id: 'app.createBreakoutRoom.captureSlides',
|
|
description: 'capture slides label',
|
|
},
|
|
captureNotesType: {
|
|
id: 'app.notes.label',
|
|
description: 'indicates notes have been captured',
|
|
},
|
|
captureSlidesType: {
|
|
id: 'app.shortcut-help.whiteboard',
|
|
description: 'indicates the whiteboard has been captured',
|
|
},
|
|
roomLabel: {
|
|
id: 'app.createBreakoutRoom.room',
|
|
description: 'Room label',
|
|
},
|
|
leastOneWarnBreakout: {
|
|
id: 'app.createBreakoutRoom.leastOneWarnBreakout',
|
|
description: 'warn message label',
|
|
},
|
|
notAssigned: {
|
|
id: 'app.createBreakoutRoom.notAssigned',
|
|
description: 'Not assigned label',
|
|
},
|
|
breakoutRoomLabel: {
|
|
id: 'app.createBreakoutRoom.breakoutRoomLabel',
|
|
description: 'breakout room label',
|
|
},
|
|
addParticipantLabel: {
|
|
id: 'app.createBreakoutRoom.addParticipantLabel',
|
|
description: 'add Participant label',
|
|
},
|
|
nextLabel: {
|
|
id: 'app.createBreakoutRoom.nextLabel',
|
|
description: 'Next label',
|
|
},
|
|
backLabel: {
|
|
id: 'app.audio.backLabel',
|
|
description: 'Back label',
|
|
},
|
|
minusRoomTime: {
|
|
id: 'app.createBreakoutRoom.minusRoomTime',
|
|
description: 'aria label for btn to decrease room time',
|
|
},
|
|
addRoomTime: {
|
|
id: 'app.createBreakoutRoom.addRoomTime',
|
|
description: 'aria label for btn to increase room time',
|
|
},
|
|
record: {
|
|
id: 'app.createBreakoutRoom.record',
|
|
description: 'label for checkbox to allow record',
|
|
},
|
|
roomTime: {
|
|
id: 'app.createBreakoutRoom.roomTime',
|
|
description: 'used to provide current room time for aria label',
|
|
},
|
|
numberOfRoomsIsValid: {
|
|
id: 'app.createBreakoutRoom.numberOfRoomsError',
|
|
description: 'Label an error message',
|
|
},
|
|
roomNameEmptyIsValid: {
|
|
id: 'app.createBreakoutRoom.emptyRoomNameError',
|
|
description: 'Label an error message',
|
|
},
|
|
roomNameDuplicatedIsValid: {
|
|
id: 'app.createBreakoutRoom.duplicatedRoomNameError',
|
|
description: 'Label an error message',
|
|
},
|
|
you: {
|
|
id: 'app.userList.you',
|
|
description: 'Text for identifying your user',
|
|
},
|
|
minimumDurationWarnBreakout: {
|
|
id: 'app.createBreakoutRoom.minimumDurationWarnBreakout',
|
|
description: 'minimum duration warning message label',
|
|
},
|
|
roomNameInputDesc: {
|
|
id: 'app.createBreakoutRoom.roomNameInputDesc',
|
|
description: 'aria description for room name change',
|
|
},
|
|
movedUserLabel: {
|
|
id: 'app.createBreakoutRoom.movedUserLabel',
|
|
description: 'screen reader alert when users are moved to rooms',
|
|
},
|
|
manageRooms: {
|
|
id: 'app.createBreakoutRoom.manageRoomsLabel',
|
|
description: 'Label for manage rooms',
|
|
},
|
|
sendInvitationToMods: {
|
|
id: 'app.createBreakoutRoom.sendInvitationToMods',
|
|
description: 'label for checkbox send invitation to moderators',
|
|
},
|
|
});
|
|
|
|
const CreateBreakoutRoom: React.FC<CreateBreakoutRoomProps> = ({
|
|
isOpen,
|
|
setIsOpen,
|
|
priority,
|
|
isUpdate = false,
|
|
isBreakoutRecordable,
|
|
users,
|
|
runningRooms,
|
|
}) => {
|
|
const { isMobile } = deviceInfo;
|
|
const intl = useIntl();
|
|
|
|
const [numberOfRoomsIsValid, setNumberOfRoomsIsValid] = React.useState(true);
|
|
const [durationIsValid, setDurationIsValid] = React.useState(true);
|
|
const [freeJoin, setFreeJoin] = React.useState(false);
|
|
const [record, setRecord] = React.useState(false);
|
|
const [captureSlides, setCaptureSlides] = React.useState(false);
|
|
const [leastOneUserIsValid, setLeastOneUserIsValid] = React.useState(false);
|
|
const [captureNotes, setCaptureNotes] = React.useState(false);
|
|
const [inviteMods, setInviteMods] = React.useState(false);
|
|
const [numberOfRooms, setNumberOfRooms] = React.useState(MIN_BREAKOUT_ROOMS);
|
|
const [durationTime, setDurationTime] = React.useState(DEFAULT_BREAKOUT_TIME);
|
|
const isImportPresentationWithAnnotationsEnabled = useIsImportPresentationWithAnnotationsFromBreakoutRoomsEnabled();
|
|
const isImportSharedNotesEnabled = useIsImportSharedNotesFromBreakoutRoomsEnabled();
|
|
|
|
const [createBreakoutRoom] = useMutation(BREAKOUT_ROOM_CREATE);
|
|
const [moveUser] = useMutation(BREAKOUT_ROOM_MOVE_USER);
|
|
|
|
const roomsRef = React.useRef<Rooms>({});
|
|
const moveRegisterRef = React.useRef<moveUserRegistery>({});
|
|
|
|
const setRoomsRef = (rooms: Rooms) => {
|
|
roomsRef.current = rooms;
|
|
};
|
|
|
|
const setMoveRegisterRef = (moveRegister: moveUserRegistery) => {
|
|
moveRegisterRef.current = moveRegister;
|
|
};
|
|
|
|
const checkboxCallbackFactory = (call: (value: boolean) => void) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const { checked } = e.target;
|
|
call(checked);
|
|
};
|
|
|
|
const createRoom = () => {
|
|
const rooms = roomsRef.current;
|
|
const roomsArray: RoomToWithSettings[] = [];
|
|
/* eslint no-restricted-syntax: "off" */
|
|
for (let i = 0; i < numberOfRooms; i += 1) {
|
|
const roomNumber = i + 1;
|
|
if (rooms[roomNumber]) {
|
|
const r = rooms[roomNumber];
|
|
roomsArray.push({
|
|
name: r.name,
|
|
sequence: r.id,
|
|
captureNotesFilename: `${r.name.replace(/\s/g, '_')}_${intl.formatMessage(intlMessages.captureNotesType)}`,
|
|
captureSlidesFilename: `${r.name.replace(/\s/g, '_')}_${intl.formatMessage(intlMessages.captureSlidesType)}`,
|
|
isDefaultName: r.name === intl.formatMessage(intlMessages.breakoutRoom, {
|
|
0: r.id,
|
|
}),
|
|
users: r.users.map((u) => u.userId),
|
|
freeJoin,
|
|
shortName: r.name,
|
|
});
|
|
} else {
|
|
const defaultName = intl.formatMessage(intlMessages.breakoutRoom, {
|
|
0: roomNumber,
|
|
});
|
|
|
|
roomsArray.push({
|
|
name: defaultName,
|
|
sequence: roomNumber,
|
|
captureNotesFilename: `${defaultName.replace(/\s/g, '_')}_${intl.formatMessage(intlMessages.captureNotesType)}`,
|
|
captureSlidesFilename: `${defaultName.replace(/\s/g, '_')}_${intl.formatMessage(intlMessages.captureSlidesType)}`,
|
|
isDefaultName: true,
|
|
freeJoin,
|
|
shortName: defaultName,
|
|
users: [],
|
|
});
|
|
}
|
|
}
|
|
createBreakoutRoom(
|
|
{
|
|
variables: {
|
|
record,
|
|
captureNotes,
|
|
captureSlides,
|
|
durationInMinutes: durationTime,
|
|
sendInviteToModerators: inviteMods,
|
|
rooms: roomsArray,
|
|
},
|
|
},
|
|
);
|
|
setIsOpen(false);
|
|
};
|
|
|
|
const userUpdate = () => {
|
|
const userIds = Object.keys(moveRegisterRef.current);
|
|
userIds.forEach((userId) => {
|
|
const { fromRoomId, toRoomId } = moveRegisterRef.current[userId];
|
|
if (fromRoomId !== toRoomId) {
|
|
moveUser({
|
|
variables: {
|
|
userId,
|
|
fromBreakoutRoomId: fromRoomId || '',
|
|
toBreakoutRoomId: toRoomId,
|
|
},
|
|
});
|
|
}
|
|
});
|
|
setIsOpen(false);
|
|
};
|
|
|
|
const title = useMemo(() => (
|
|
<Styled.SubTitle>
|
|
{isUpdate
|
|
? intl.formatMessage(intlMessages.breakoutRoomUpdateDesc)
|
|
: intl.formatMessage(intlMessages.breakoutRoomDesc)}
|
|
</Styled.SubTitle>
|
|
), [isUpdate]);
|
|
|
|
const checkboxesInfo = useMemo(() => {
|
|
return [
|
|
{
|
|
allowed: true,
|
|
htmlFor: 'freeJoinCheckbox',
|
|
key: 'free-join-breakouts',
|
|
id: 'freeJoinCheckbox',
|
|
onChange: checkboxCallbackFactory((e: boolean) => {
|
|
setFreeJoin(e);
|
|
setLeastOneUserIsValid(true);
|
|
}),
|
|
label: intl.formatMessage(intlMessages.freeJoinLabel),
|
|
},
|
|
{
|
|
allowed: isBreakoutRecordable,
|
|
htmlFor: 'recordBreakoutCheckbox',
|
|
key: 'record-breakouts',
|
|
id: 'recordBreakoutCheckbox',
|
|
onChange: checkboxCallbackFactory(setRecord),
|
|
label: intl.formatMessage(intlMessages.record),
|
|
},
|
|
{
|
|
allowed: isImportPresentationWithAnnotationsEnabled,
|
|
htmlFor: 'captureSlidesBreakoutCheckbox',
|
|
key: 'capture-slides-breakouts',
|
|
id: 'captureSlidesBreakoutCheckbox',
|
|
onChange: checkboxCallbackFactory(setCaptureSlides),
|
|
label: intl.formatMessage(intlMessages.captureSlidesLabel),
|
|
},
|
|
{
|
|
allowed: isImportSharedNotesEnabled,
|
|
htmlFor: 'captureNotesBreakoutCheckbox',
|
|
key: 'capture-notes-breakouts',
|
|
id: 'captureNotesBreakoutCheckbox',
|
|
onChange: checkboxCallbackFactory(setCaptureNotes),
|
|
label: intl.formatMessage(intlMessages.captureNotesLabel),
|
|
},
|
|
{
|
|
allowed: true,
|
|
htmlFor: 'sendInvitationToAssignedModeratorsCheckbox',
|
|
key: 'send-invitation-to-assigned-moderators-breakouts',
|
|
id: 'sendInvitationToAssignedModeratorsCheckbox',
|
|
onChange: checkboxCallbackFactory(setInviteMods),
|
|
label: intl.formatMessage(intlMessages.sendInvitationToMods),
|
|
},
|
|
];
|
|
}, [isBreakoutRecordable]);
|
|
|
|
const form = useMemo(() => {
|
|
if (isUpdate) return null;
|
|
|
|
const BREAKOUT_LIM = window.meetingClientSettings.public.app.breakouts.breakoutRoomLimit;
|
|
const MAX_BREAKOUT_ROOMS = BREAKOUT_LIM > MIN_BREAKOUT_ROOMS ? BREAKOUT_LIM : MIN_BREAKOUT_ROOMS;
|
|
|
|
return (
|
|
<React.Fragment key="breakout-form">
|
|
<Styled.BreakoutSettings>
|
|
<div>
|
|
<Styled.FormLabel valid={numberOfRoomsIsValid} aria-hidden>
|
|
{intl.formatMessage(intlMessages.numberOfRooms)}
|
|
</Styled.FormLabel>
|
|
<Styled.InputRooms
|
|
id="numberOfRooms"
|
|
name="numberOfRooms"
|
|
valid={numberOfRoomsIsValid}
|
|
value={numberOfRooms}
|
|
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
const { value } = e.target;
|
|
setNumberOfRooms(Number.parseInt(value, 10));
|
|
setNumberOfRoomsIsValid(true);
|
|
}}
|
|
aria-label={intl.formatMessage(intlMessages.numberOfRooms)}
|
|
>
|
|
{
|
|
range(MIN_BREAKOUT_ROOMS, MAX_BREAKOUT_ROOMS + 1).map((item) => (<option key={uniqueId('value-')}>{item}</option>))
|
|
}
|
|
</Styled.InputRooms>
|
|
</div>
|
|
<Styled.DurationLabel valid={durationIsValid} htmlFor="breakoutRoomTime">
|
|
<Styled.LabelText bold={false} aria-hidden>
|
|
{intl.formatMessage(intlMessages.duration)}
|
|
</Styled.LabelText>
|
|
<Styled.DurationArea>
|
|
<Styled.DurationInput
|
|
type="number"
|
|
min="1"
|
|
value={durationTime}
|
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const { value } = e.target;
|
|
const v = Number.parseInt(value, 10);
|
|
setDurationTime(v);
|
|
setDurationIsValid(v >= MIN_BREAKOUT_TIME);
|
|
}}
|
|
onBlur={(e: React.FocusEvent<HTMLInputElement>) => {
|
|
const { value } = e.target;
|
|
const v = Number.parseInt(value, 10);
|
|
setDurationTime((v && !(v <= 0)) ? v : MIN_BREAKOUT_TIME);
|
|
setDurationIsValid(true);
|
|
}}
|
|
aria-label={intl.formatMessage(intlMessages.duration)}
|
|
data-test="durationTime"
|
|
/>
|
|
</Styled.DurationArea>
|
|
<Styled.SpanWarn data-test="minimumDurationWarnBreakout" valid={durationIsValid}>
|
|
{
|
|
intl.formatMessage(
|
|
intlMessages.minimumDurationWarnBreakout,
|
|
{ 0: MIN_BREAKOUT_TIME },
|
|
)
|
|
}
|
|
</Styled.SpanWarn>
|
|
</Styled.DurationLabel>
|
|
<Styled.CheckBoxesContainer key="breakout-checkboxes">
|
|
{checkboxesInfo
|
|
.filter((item) => item.allowed)
|
|
.map((item) => (
|
|
<Styled.FreeJoinLabel htmlFor={item.htmlFor} key={item.key}>
|
|
<Styled.FreeJoinCheckbox
|
|
type="checkbox"
|
|
id={item.id}
|
|
onChange={item.onChange}
|
|
aria-label={item.label}
|
|
/>
|
|
<span aria-hidden>{item.label}</span>
|
|
</Styled.FreeJoinLabel>
|
|
))}
|
|
</Styled.CheckBoxesContainer>
|
|
</Styled.BreakoutSettings>
|
|
<Styled.SpanWarn valid={numberOfRoomsIsValid}>
|
|
{intl.formatMessage(intlMessages.numberOfRoomsIsValid)}
|
|
</Styled.SpanWarn>
|
|
<span aria-hidden id="randomlyAssignDesc" className="sr-only">
|
|
{intl.formatMessage(intlMessages.randomlyAssignDesc)}
|
|
</span>
|
|
<Styled.Separator />
|
|
</React.Fragment>
|
|
);
|
|
}, [durationTime, durationIsValid, numberOfRooms, numberOfRoomsIsValid]);
|
|
|
|
return (
|
|
<ModalFullscreen
|
|
title={
|
|
isUpdate
|
|
? intl.formatMessage(intlMessages.updateTitle)
|
|
: intl.formatMessage(intlMessages.breakoutRoomTitle)
|
|
}
|
|
confirm={
|
|
{
|
|
label: isUpdate
|
|
? intl.formatMessage(intlMessages.updateConfirm)
|
|
: intl.formatMessage(intlMessages.confirmButton),
|
|
callback: isUpdate ? userUpdate : createRoom,
|
|
disabled: !leastOneUserIsValid || !numberOfRoomsIsValid || !durationIsValid,
|
|
}
|
|
}
|
|
dismiss={{
|
|
label: isUpdate
|
|
? intl.formatMessage(intlMessages.cancelLabel)
|
|
: intl.formatMessage(intlMessages.dismissLabel),
|
|
callback: () => setIsOpen(false),
|
|
disabled: false,
|
|
}}
|
|
isOpen={isOpen}
|
|
priority={priority}
|
|
>
|
|
<Styled.Content>
|
|
{title}
|
|
{form}
|
|
<RoomManagmentState
|
|
numberOfRooms={numberOfRooms}
|
|
users={users}
|
|
RendererComponent={isMobile ? BreakoutRoomUserAssignmentMobile : BreakoutRoomUserAssignment}
|
|
runningRooms={runningRooms}
|
|
setRoomsRef={setRoomsRef}
|
|
setMoveRegisterRef={setMoveRegisterRef}
|
|
setFormIsValid={setLeastOneUserIsValid}
|
|
/>
|
|
</Styled.Content>
|
|
</ModalFullscreen>
|
|
);
|
|
};
|
|
|
|
const CreateBreakoutRoomContainer: React.FC<CreateBreakoutRoomContainerProps> = ({
|
|
isOpen,
|
|
setIsOpen,
|
|
priority,
|
|
isUpdate = false,
|
|
}) => {
|
|
const [fetchedBreakouts, setFetchedBreakouts] = React.useState(false);
|
|
// isBreakoutRecordable - get from meeting breakout policies breakoutPolicies/record
|
|
const {
|
|
data: currentMeeting,
|
|
} = useMeeting((m) => ({
|
|
breakoutPolicies: m.breakoutPolicies,
|
|
}));
|
|
|
|
const {
|
|
data: usersData,
|
|
loading: usersLoading,
|
|
error: usersError,
|
|
} = useQuery<getUserResponse>(getUser, {
|
|
fetchPolicy: 'network-only',
|
|
});
|
|
|
|
const [
|
|
loadBreakouts,
|
|
{
|
|
data: breakoutsData,
|
|
loading: breakoutsLoading,
|
|
error: breakoutsError,
|
|
},
|
|
] = useLazyQuery<getBreakoutsResponse>(getBreakouts, {
|
|
fetchPolicy: 'network-only',
|
|
});
|
|
|
|
if (usersLoading || breakoutsLoading || !currentMeeting) {
|
|
return null;
|
|
}
|
|
|
|
if (true && !fetchedBreakouts) {
|
|
loadBreakouts();
|
|
setFetchedBreakouts(true);
|
|
}
|
|
|
|
if (true && breakoutsLoading) return null;
|
|
|
|
if (usersError || breakoutsError) {
|
|
logger.info('Error loading users', usersError);
|
|
logger.info('Error loading breakouts', breakoutsError);
|
|
return (
|
|
<div>
|
|
{JSON.stringify(usersError) || JSON.stringify(breakoutsError)}
|
|
</div>
|
|
);
|
|
}
|
|
return (
|
|
<CreateBreakoutRoom
|
|
isOpen={isOpen}
|
|
setIsOpen={setIsOpen}
|
|
priority={priority}
|
|
isUpdate={isUpdate}
|
|
isBreakoutRecordable={currentMeeting?.breakoutPolicies?.record ?? true}
|
|
users={usersData?.user ?? []}
|
|
runningRooms={breakoutsData?.breakoutRoom ?? []}
|
|
/>
|
|
);
|
|
};
|
|
|
|
export default CreateBreakoutRoomContainer;
|