import { generateRandomPassword, isRecurringMeeting, MeetingType, updateFullTime, updateFullYear, } from '@ringcentral-integration/commons/helpers/meetingHelper'; import type { MeetingDelegator, RcmItemType, RcMMeetingModel, } from '@ringcentral-integration/commons/modules/Meeting'; import { ASSISTED_USERS_MYSELF, RCM_ITEM_NAME, } from '@ringcentral-integration/commons/modules/Meeting'; import { format } from '@ringcentral-integration/utils'; import type { RcCheckboxProps, RcDatePickerProps, RcTimePickerProps, } from '@ringcentral/juno'; import { RcAlert, RcCheckbox, RcDatePicker, RcIcon, RcLink, RcMenuItem, RcSelect, RcTextField, RcTimePicker, RcTypography, spacing, styled, } from '@ringcentral/juno'; import { LockBorder as lockSvg } from '@ringcentral/juno-icon'; import clsx from 'clsx'; import type { FunctionComponent } from 'react'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import { formatMeetingId } from '../../lib/MeetingCalendarHelper'; import { getHoursList, getMinutesList, HOUR_SCALE, MINUTE_SCALE, } from '../../lib/MeetingHelper'; import { MigrateToPluginAlert, RemoveMeetingWarn } from '../MeetingAlert'; import { SpinnerOverlay } from '../SpinnerOverlay'; import { ExtendedTooltip as MeetingOptionLocked } from './ExtendedTooltip'; import { VideoSettingGroup } from './VideoSettingGroup'; import i18n, { type I18nKey } from './i18n'; import styles from './styles.scss'; export interface MeetingConfigsProps { disabled: boolean; showSpinnerInConfigPanel: boolean; personalMeetingId?: string; updateMeetingSettings: (meeting: Partial) => void; switchUsePersonalMeetingId: (usePersonalMeetingId: boolean) => any; update?: (...args: any[]) => any; init: (...args: any[]) => any; meeting: RcMMeetingModel; currentLocale: string; recipientsSection?: React.ReactNode; labelPlacement?: 'end' | 'start' | 'top' | 'bottom'; showTopic?: boolean; showWhen?: boolean; showDuration?: boolean; showRecurringMeeting?: boolean; meetingOptionToggle?: boolean; showScheduleOnBehalf?: boolean; passwordPlaceholderEnable?: boolean; audioOptionToggle?: boolean; useTimePicker?: boolean; delegators: MeetingDelegator[]; updateScheduleFor: (userExtensionId: string) => any; trackSettingChanges?: (itemName: RcmItemType) => void; onCloseMigrationAlert?: () => void; enableServiceWebSettings?: boolean; datePickerSize?: RcDatePickerProps['size']; timePickerSize?: RcTimePickerProps['size']; checkboxSize?: RcCheckboxProps['size']; recurringMeetingPosition?: 'middle' | 'bottom'; defaultTopic: string; showMigrationAlert?: boolean; showRemoveMeetingWarning?: boolean; brandConfig?: any; } function getHelperTextForPasswordField( meeting: RcMMeetingModel, currentLocale: string, isPasswordFocus: boolean, ) { if (!meeting.password) { return i18n.getString('passwordEmptyError', currentLocale); } if (!meeting.isMeetingPasswordValid) { return i18n.getString('rcmPasswordInvalidError', currentLocale); } if (isPasswordFocus) { return i18n.getString('rcmPasswordHintText', currentLocale); } // when correct input without focus, show nothing return ''; } function getCheckboxCommProps( labelPlacement: 'end' | 'start' | 'top' | 'bottom', ) { return { formControlLabelProps: { classes: { root: labelPlacement === 'end' ? styles.labelPlacementEnd : styles.labelPlacementStart, label: styles.fullWidthLabel, }, labelPlacement, }, }; } const MeetingOptionLabel: FunctionComponent<{ children: React.ReactNode; isLocked?: boolean; currentLocale?: string; hasScrollBar?: boolean; className?: string; labelPlacement?: string; dataSign?: string; }> = ({ children, labelPlacement, isLocked = false, currentLocale, hasScrollBar = false, className = '', dataSign = '', }) => { return (
{children}
{isLocked ? (
) : null}
); }; const PanelRoot = styled.div` ${RcCheckbox} { padding: ${spacing(2)}; } `; export const MeetingConfigs: FunctionComponent = ({ showRecurringMeeting = true, labelPlacement = 'start', datePickerSize = 'medium', timePickerSize = 'medium', checkboxSize = 'medium', updateMeetingSettings, disabled, personalMeetingId, switchUsePersonalMeetingId, init, meeting, children, currentLocale, recipientsSection, showTopic, showWhen, showDuration, meetingOptionToggle, audioOptionToggle, useTimePicker, showScheduleOnBehalf, delegators, updateScheduleFor, trackSettingChanges, onCloseMigrationAlert, showSpinnerInConfigPanel, enableServiceWebSettings, recurringMeetingPosition, defaultTopic, showMigrationAlert, showRemoveMeetingWarning, brandConfig, }) => { useEffect(() => { if (init) { init(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const update = (options: any) => { return updateMeetingSettings({ ...meeting, ...options, }); }; const trackItemChanges = (itemName: RcmItemType) => { trackSettingChanges && trackSettingChanges(itemName); }; const configRef = useRef(); const [hasScrollBar, setHasScrollBar] = useState(false); useEffect(() => { setHasScrollBar( // @ts-expect-error TS(2532): Object is possibly 'undefined'. configRef.current.scrollHeight > configRef.current.clientHeight, ); }, []); /* Password */ const [isPasswordFocus, setPasswordFocus] = useState(false); /* AudioOptions */ const [audioOptions, setAudioOptions] = useState( meeting.audioOptions && meeting.audioOptions.join('_'), ); const enableThirdPartyAudio = meeting?.telephonyUserSettings?.thirdPartyAudio; const audioHelpTextMap: { [key: string]: string } = { Phone: 'telephonyOnly', ComputerAudio: 'voIPOnly', Phone_ComputerAudio: 'both', ThirdParty: 'thirdParty', }; const updateAudioOptions = (audioOptions: string) => { setAudioOptions(audioOptions); update({ audioOptions: audioOptions.split('_'), }); }; useEffect(() => { setAudioOptions(meeting.audioOptions.join('_')); }, [meeting.audioOptions]); /* Recurring */ const [isRecurring, setIsRecurring] = useState( isRecurringMeeting(meeting.meetingType), ); const toggleRecurring = (isRecurring: boolean) => { update({ meetingType: isRecurring ? MeetingType.RECURRING : MeetingType.SCHEDULED, }); }; useEffect(() => { setIsRecurring(isRecurringMeeting(meeting.meetingType)); }, [meeting.meetingType]); /* Use Personal MeetingId */ const [isPmiConfirm, setPmiConfirm] = useState(false); const onPmiChange = async (usePersonalMeetingId: boolean) => { setPmiConfirm(false); await switchUsePersonalMeetingId(usePersonalMeetingId); }; /* Option Disable Status */ const isDisabled = disabled || (meeting.usePersonalMeetingId && !isPmiConfirm); const settingsGroupExpandable = false; // FIXME: Argument of type '"end" | "start" | "top" | "botto... Remove this comment to see the full error message const checkboxCommProps = getCheckboxCommProps(labelPlacement); const startTime = useMemo(() => { // @ts-expect-error TS(2532): Object is possibly 'undefined'. return new Date(meeting.schedule.startTime); // @ts-expect-error TS(2532): Object is possibly 'undefined'. }, [meeting.schedule.startTime]); const hoursList = getHoursList(HOUR_SCALE, currentLocale); const minutesList = getMinutesList(MINUTE_SCALE, currentLocale); return ( // @ts-expect-error TS(2322): Type '{ children: Element; ref: MutableRefObject
{showSpinnerInConfigPanel ? : null} {showRemoveMeetingWarning && ( )} {showMigrationAlert && ( )} {showTopic ? (
{children}
) : null} {recipientsSection ? (
{recipientsSection}
) : null}
{showWhen && !isRecurring ? (
{ update({ schedule: { ...meeting.schedule, // @ts-expect-error TS(2345): Argument of type 'Date | null' is not assignable t... Remove this comment to see the full error message startTime: updateFullYear(startTime, value), }, }); }} />
{ update({ schedule: { ...meeting.schedule, // @ts-expect-error TS(2345): Argument of type 'Date | null' is not assignable t... Remove this comment to see the full error message startTime: updateFullTime(startTime, value), }, }); }} />
) : null} {showDuration && !isRecurring ? (
{ // @ts-expect-error TS(2571): Object is of type 'unknown'. const value = +e.target.value; const restMinutes = Math.floor( // @ts-expect-error TS(2532): Object is possibly 'undefined'. meeting.schedule.durationInMinutes % 60, ); const durationInMinutes = value * 60 + restMinutes; update({ schedule: { ...meeting.schedule, durationInMinutes, }, }); }} className={styles.select} label={i18n.getString('duration', currentLocale)} > {hoursList.map((item, i) => ( {/* @ts-expect-error TS(2339): Property 'text' does not exist on type 'never'. */} {item !== null ? item.text : 'defaultValue'} ))}
{ // @ts-expect-error TS(2571): Object is of type 'unknown'. const value = +e.target.value; const restHours = Math.floor( // @ts-expect-error TS(2532): Object is possibly 'undefined'. meeting.schedule.durationInMinutes / 60, ); // @ts-expect-error TS(2339): Property 'value' does not exist on type 'never'. const isMax = restHours === hoursList.slice(-1)[0].value; const minutes = isMax ? 0 : value; const durationInMinutes = restHours * 60 + minutes; update({ schedule: { ...meeting.schedule, durationInMinutes, }, }); }} > {minutesList.map((item, i) => ( {/* @ts-expect-error TS(2339): Property 'text' does not exist on type 'never'. */} {item !== null ? item.text : 'defaultValue'} ))}
) : null} {showRecurringMeeting && recurringMeetingPosition === 'middle' ? ( { toggleRecurring(!isRecurring); }} label={ {i18n.getString('recurringMeeting', currentLocale)} } /> {isRecurring ? ( {i18n.getString('recurringDescribe', currentLocale)} ) : null} ) : null} {showScheduleOnBehalf ? (
{ updateScheduleFor(e.target.value as string); trackItemChanges(RCM_ITEM_NAME.scheduleFor); }} value={meeting.host.id} > {delegators.map((item: MeetingDelegator, index: number) => { const userName = item.name === ASSISTED_USERS_MYSELF ? i18n.getString(item.name, currentLocale) : item.name; return ( {userName} ); })}
) : null} {personalMeetingId ? ( <> { onPmiChange(!meeting.usePersonalMeetingId); }} label={ {i18n.getString('usePersonalMeetingId', currentLocale)}   {formatMeetingId(personalMeetingId, '-')} } /> {meeting.usePersonalMeetingId ? ( {isPmiConfirm ? ( i18n.getString('pmiSettingChangeAlert', currentLocale) ) : ( <> {i18n.getString('pmiChangeConfirm', currentLocale)} setPmiConfirm(!isPmiConfirm)} data-sign="setPmiConfirm" > {i18n.getString('changePmiSettings', currentLocale)} )} ) : null} ) : null} { let password = ''; // checked before if (meeting._requireMeetingPassword) { password = ''; } else { password = meeting.usePersonalMeetingId && meeting._pmiPassword ? meeting._pmiPassword : generateRandomPassword(); } update({ _requireMeetingPassword: !meeting._requireMeetingPassword, password, }); trackItemChanges(RCM_ITEM_NAME._requireMeetingPassword); }} label={ {i18n.getString('requirePassword', currentLocale)} } /> {meeting._requireMeetingPassword ? (
{ const password = e.target.value; update({ password }); }} onFocus={() => { setPasswordFocus(true); }} onBlur={() => { setPasswordFocus(false); trackItemChanges(RCM_ITEM_NAME.password); }} />
) : null}
{ update({ startParticipantsVideo: !meeting.startParticipantsVideo, }); }} label={ {i18n.getString('turnOffCamera', currentLocale)} } /> { update({ startHostVideo: !meeting.startHostVideo, }); }} label={ {i18n.getString('turnOffHostCamera', currentLocale)} } />
{ updateAudioOptions(e.target.value as string); }} value={audioOptions} > {i18n.getString('telephonyOnly', currentLocale)} {i18n.getString('voIPOnly', currentLocale)} {i18n.getString('both', currentLocale)} {i18n.getString('thirdParty', currentLocale)}
{enableServiceWebSettings && meeting.settingLock?.audioOptions ? (
) : null}
{ update({ allowJoinBeforeHost: !meeting.allowJoinBeforeHost, }); trackItemChanges(RCM_ITEM_NAME.allowJoinBeforeHost); }} label={ {i18n.getString('joinBeforeHost', currentLocale)} } /> {showRecurringMeeting && recurringMeetingPosition === 'bottom' ? ( <> { toggleRecurring(!isRecurring); }} label={ {i18n.getString('recurringMeeting', currentLocale)} } /> {i18n.getString('recurringNote', currentLocale)} ) : null}
); }; MeetingConfigs.defaultProps = {};