├── src ├── bootstrap.js ├── components │ ├── MeetingHomePanel │ │ ├── interface.ts │ │ ├── i18n │ │ │ ├── loadLocale.ts │ │ │ ├── index.ts │ │ │ └── en-US.ts │ │ ├── index.ts │ │ └── JoinDialog.tsx │ ├── MainViewPanel │ │ └── i18n │ │ │ ├── loadLocale.js │ │ │ ├── index.js │ │ │ ├── en-US.js │ │ │ ├── zh-CN.js │ │ │ ├── zh-HK.js │ │ │ ├── zh-TW.js │ │ │ ├── ja-JP.js │ │ │ ├── en-AU.js │ │ │ ├── en-CA.js │ │ │ ├── en-GB.js │ │ │ ├── de-DE.js │ │ │ ├── fr-FR.js │ │ │ ├── it-IT.js │ │ │ ├── pt-BR.js │ │ │ ├── es-419.js │ │ │ ├── es-ES.js │ │ │ └── fr-CA.js │ ├── MeetingItem │ │ ├── i18n │ │ │ ├── loadLocale.js │ │ │ ├── en-US.js │ │ │ └── index.js │ │ └── styles.scss │ ├── SettingsPanel │ │ ├── i18n │ │ │ ├── loadLocale.ts │ │ │ ├── index.ts │ │ │ └── en-US.ts │ │ └── index.tsx │ ├── MeetingInviteModal │ │ └── i18n │ │ │ ├── loadLocale.ts │ │ │ ├── en-US.ts │ │ │ └── index.ts │ ├── SearchAndFilter │ │ └── i18n │ │ │ ├── loadLocale.ts │ │ │ ├── en-US.ts │ │ │ └── index.ts │ ├── DialerPanel │ │ ├── FromField │ │ │ └── i18n │ │ │ │ ├── loadLocale.ts │ │ │ │ ├── index.ts │ │ │ │ ├── en-US.ts │ │ │ │ ├── ja-JP.ts │ │ │ │ ├── ko-KR.ts │ │ │ │ ├── zh-HK.ts │ │ │ │ ├── zh-TW.ts │ │ │ │ ├── zh-CN.ts │ │ │ │ ├── fi-FI.ts │ │ │ │ ├── nl-NL.ts │ │ │ │ ├── es-ES.ts │ │ │ │ ├── it-IT.ts │ │ │ │ ├── pt-BR.ts │ │ │ │ ├── fr-FR.ts │ │ │ │ ├── pt-PT.ts │ │ │ │ ├── en-AU.ts │ │ │ │ ├── en-GB.ts │ │ │ │ ├── es-419.ts │ │ │ │ ├── fr-CA.ts │ │ │ │ └── de-DE.ts │ │ └── StyledRecipientsInput.tsx │ ├── MeetingHistoryPanel │ │ └── i18n │ │ │ ├── loadLocale.ts │ │ │ ├── index.ts │ │ │ └── en-US.ts │ ├── UpcomingMeetingList │ │ └── i18n │ │ │ ├── loadLocale.js │ │ │ ├── index.js │ │ │ └── en-US.js │ ├── RcVideoScheduleButton │ │ └── index.ts │ ├── ActionMenu │ │ └── index.tsx │ ├── MeetingScheduleButton │ │ ├── index.ts │ │ └── MeetingScheduleButtonWrapper.tsx │ ├── CallCtrlPanel │ │ └── ActiveCallPad │ │ │ └── actions.ts │ ├── ThirdPartyContactSourceIcon │ │ └── index.tsx │ ├── NavigationBar │ │ ├── TabIcon.tsx │ │ ├── interface.ts │ │ └── helper.tsx │ ├── SubTabsView │ │ └── index.tsx │ ├── TransferPanel │ │ └── CallButton.tsx │ ├── GlipChatPanel │ │ └── getGlipGroupName.ts │ ├── CallItem │ │ └── StatusMessage.tsx │ ├── ActiveCallItem │ │ └── CallIcon.tsx │ ├── ConfirmDialog │ │ └── index.tsx │ ├── WidgetAppsPanel │ │ └── styled.ts │ ├── AdditionalToolbarButton │ │ └── index.tsx │ ├── ComposeTextPanel │ │ └── NoTextPermission.tsx │ ├── SaveButton │ │ └── index.tsx │ ├── DemoOnlyBanner │ │ └── index.tsx │ ├── ConferenceDialerPanel │ │ └── index.tsx │ ├── SmartNotesPanel │ │ ├── SmartNoteApp.tsx │ │ └── index.tsx │ ├── InitializeAudioBanner │ │ └── index.tsx │ ├── AudioSettingsPanel │ │ └── AudioSettingsPanel.interface.ts │ └── BackHeader │ │ └── index.tsx ├── containers │ ├── MeetingTabContainer │ │ ├── i18n │ │ │ ├── loadLocale.js │ │ │ ├── en-US.js │ │ │ └── index.js │ │ └── index.tsx │ ├── DialerPage │ │ └── index.ts │ ├── ContactsPage │ │ └── index.ts │ ├── ComposeTextPage │ │ └── index.ts │ ├── CallCtrlPage │ │ └── index.ts │ ├── CallsListPage │ │ └── index.js │ ├── AudioSettingsPage │ │ └── index.ts │ ├── CallDetailsPage │ │ └── index.ts │ ├── ContactDetailsPage │ │ └── index.ts │ ├── CallingSettingsPage │ │ └── index.ts │ ├── LogCallPage │ │ └── index.ts │ ├── CallQueueSettingsPage │ │ └── index.ts │ ├── SettingsPage │ │ └── index.js │ ├── MainView │ │ └── index.js │ ├── MessageDetailsPage │ │ └── index.ts │ ├── SideDrawerContainer │ │ └── index.ts │ ├── GlipChatPage │ │ └── index.tsx │ ├── LogMessagesPage │ │ └── index.ts │ ├── SmartNotesPage │ │ └── index.ts │ ├── WidgetAppsPage │ │ └── index.ts │ ├── CustomizedPage │ │ └── index.ts │ ├── GlipGroupsPage │ │ └── index.ts │ ├── PhoneTabsContainer │ │ └── index.ts │ ├── MeetingHomePage │ │ └── index.ts │ ├── IncomingCallContainer │ │ └── index.ts │ ├── GenericMeetingPage │ │ └── index.ts │ ├── MeetingHistoryPage │ │ └── index.ts │ ├── ThemeSettingPage │ │ └── index.ts │ ├── MeetingInviteModal │ │ └── index.js │ ├── RingtoneSettingsPage │ │ └── index.ts │ ├── ConversationsPage │ │ └── index.ts │ ├── OtherDeviceCallCtrlPage │ │ └── index.ts │ ├── ConversationPage │ │ └── index.ts │ ├── TransferPage │ │ └── index.ts │ ├── ThirdPartySettingSectionPage │ │ └── index.ts │ ├── NotificationContainer │ │ └── index.ts │ ├── ConferenceCallDialerPage │ │ └── index.ts │ └── ThirdPartyMeetingScheduleButton │ │ └── index.js ├── lib │ ├── themes │ │ ├── index.ts │ │ ├── btRich │ │ │ └── index.ts │ │ ├── rcBlue │ │ │ └── index.ts │ │ ├── rcDark │ │ │ └── index.ts │ │ ├── attRich │ │ │ └── index.ts │ │ ├── atosRich │ │ │ └── index.ts │ │ ├── telusRich │ │ │ └── index.ts │ │ ├── rainbowRich │ │ │ └── index.ts │ │ ├── rcJupiterBlue │ │ │ └── index.ts │ │ ├── avayaCustomized │ │ │ └── index.ts │ │ ├── rcHighContrast │ │ │ └── index.ts │ │ ├── variables │ │ │ ├── rc.ts │ │ │ ├── telus.ts │ │ │ ├── att.ts │ │ │ ├── bt.ts │ │ │ ├── atos.ts │ │ │ ├── avaya.ts │ │ │ ├── rainbow.ts │ │ │ └── index.ts │ │ └── theme.ts │ ├── isFirefox.js │ ├── Adapter │ │ └── messageTypes.js │ ├── renderContactName.js │ ├── patchOpenWindow.js │ ├── hackSend.js │ ├── parseUri.js │ ├── contactMatchHelper.js │ ├── callHelper.ts │ ├── TabFreezePrevention.ts │ ├── lockRefresh.js │ ├── isDuplicated.ts │ ├── patchGetUserMedia.js │ ├── requestWithPostMessage.js │ ├── searchContactPhoneNumbers.js │ ├── widgetContact.ts │ ├── notification.js │ ├── popWindow.ts │ └── conversationHelper.ts ├── proxy.js ├── modules │ ├── CallQueues │ │ ├── index.ts │ │ └── CallQueue.interface.ts │ ├── SmsTemplates │ │ ├── index.ts │ │ └── SmsTemplates.interface.ts │ ├── CallQueuePresence │ │ ├── index.ts │ │ └── CallQueuePresence.interface.ts │ ├── Adapter │ │ ├── actionTypes.js │ │ └── getReducer.js │ ├── GlipPersons │ │ └── index.js │ ├── MessageSender │ │ └── index.ts │ ├── NumberValidate │ │ └── index.ts │ ├── SimpleCallControlUI │ │ └── index.ts │ ├── GlipCompany │ │ └── index.js │ ├── GlipGroups │ │ └── index.js │ ├── CallQueueSettingsUI │ │ └── index.ts │ ├── MeetingInviteModalUI │ │ └── index.ts │ ├── ContactListUI │ │ └── index.js │ ├── WidgetAppsUI │ │ └── index.ts │ ├── AlertUI │ │ └── index.ts │ ├── CompanyContacts │ │ └── index.ts │ ├── ThirdPartySettingSectionUI │ │ └── index.ts │ ├── CallingSettings │ │ └── index.js │ ├── Webphone │ │ └── helper.ts │ ├── IncomingCallUI │ │ └── index.ts │ ├── Analytics │ │ └── trackEvents.ts │ ├── GenericMeeting │ │ └── index.ts │ ├── TabManager │ │ └── index.ts │ ├── ContactMatcher │ │ └── index.ts │ ├── CallLogSection │ │ └── index.js │ ├── DateTimeFormat │ │ └── index.ts │ └── ThemeSettingUI │ │ └── index.ts ├── assets │ ├── atos │ │ └── icon.png │ ├── images │ │ ├── favicon.ico │ │ ├── feedback.svg │ │ ├── popup.svg │ │ └── help.svg │ ├── rainbow │ │ └── icon.png │ └── avaya │ │ └── icon.svg ├── redirect.js ├── proxy.html └── redirect.html ├── test ├── setup.js ├── steps │ ├── embeddable.js │ └── common.js ├── helpers │ └── index.js ├── index.test.js └── embeddable.test.js ├── docs ├── extra.js ├── assets │ ├── voicemail-drop.png │ ├── call-widget-apps.png │ ├── call-widget-auth-button.png │ ├── call-widget-case-demo.png │ ├── embeddable-3-side-panel.png │ ├── voicemail-drop-setting.png │ ├── call-widget-contact-notes.png │ └── call-widget-action-buttons.png ├── config │ ├── user-agent.md │ ├── badge.md │ ├── setting-params.md │ ├── styles.md │ ├── prefix.md │ └── environment.md ├── self-hosting.md ├── integration │ ├── analytics.md │ ├── sms-toolbar-button.md │ ├── call-pop.md │ ├── click-to-dial.md │ ├── vcard-clicks.md │ ├── log-video-meeting.md │ ├── new-latest-uri.md │ └── index.md └── extra.css ├── jest.setup.js ├── .env.default ├── getBrandConfig ├── atos │ └── theme.scss ├── avaya │ └── theme.scss ├── rainbow │ └── theme.scss ├── rc │ └── theme.scss ├── theme.scss ├── att │ └── theme.scss ├── bt │ └── theme.scss ├── telus │ └── theme.scss └── index.js ├── release.sh ├── requirements.txt ├── overrides └── main.html ├── .env.test.default ├── packages └── jsonschema-page │ ├── .storybook │ ├── manager.js │ └── preview.ts │ ├── src │ ├── Fields │ │ ├── index.ts │ │ ├── Button.tsx │ │ ├── Alert.tsx │ │ ├── Typography.tsx │ │ └── Link.tsx │ ├── Templates │ │ ├── FieldHelpTemplate.tsx │ │ ├── AddButton.tsx │ │ ├── FieldErrorTemplate.tsx │ │ ├── SubmitButton.tsx │ │ ├── DescriptionField.tsx │ │ ├── ErrorList.tsx │ │ └── index.ts │ ├── Widgets │ │ └── index.tsx │ └── index.tsx │ └── .npmignore ├── .gitignore ├── jest-puppeteer.config.js ├── jest.config.js ├── babel.config.js └── LICENSE /src/bootstrap.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | // Setup 2 | -------------------------------------------------------------------------------- /docs/extra.js: -------------------------------------------------------------------------------- 1 | console.log('From extra.js'); 2 | -------------------------------------------------------------------------------- /src/components/MeetingHomePanel/interface.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/MainViewPanel/i18n/loadLocale.js: -------------------------------------------------------------------------------- 1 | /* loadLocale */ 2 | -------------------------------------------------------------------------------- /src/components/MeetingItem/i18n/loadLocale.js: -------------------------------------------------------------------------------- 1 | /* loadLocale */ 2 | -------------------------------------------------------------------------------- /src/components/SettingsPanel/i18n/loadLocale.ts: -------------------------------------------------------------------------------- 1 | /* loadLocale */ 2 | -------------------------------------------------------------------------------- /src/components/MeetingHomePanel/i18n/loadLocale.ts: -------------------------------------------------------------------------------- 1 | /* loadLocale */ 2 | -------------------------------------------------------------------------------- /src/components/MeetingInviteModal/i18n/loadLocale.ts: -------------------------------------------------------------------------------- 1 | /* loadLocale */ 2 | -------------------------------------------------------------------------------- /src/components/SearchAndFilter/i18n/loadLocale.ts: -------------------------------------------------------------------------------- 1 | /* loadLocale */ 2 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/loadLocale.ts: -------------------------------------------------------------------------------- 1 | /* loadLocale */ 2 | -------------------------------------------------------------------------------- /src/components/MeetingHistoryPanel/i18n/loadLocale.ts: -------------------------------------------------------------------------------- 1 | /* loadLocale */ 2 | -------------------------------------------------------------------------------- /src/components/SettingsPanel/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './SettingsPanel'; 2 | -------------------------------------------------------------------------------- /src/components/UpcomingMeetingList/i18n/loadLocale.js: -------------------------------------------------------------------------------- 1 | /* loadLocale */ 2 | -------------------------------------------------------------------------------- /src/containers/MeetingTabContainer/i18n/loadLocale.js: -------------------------------------------------------------------------------- 1 | /* loadLocale */ 2 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | require('expect-puppeteer'); 2 | 3 | jest.setTimeout(400000); 4 | -------------------------------------------------------------------------------- /.env.default: -------------------------------------------------------------------------------- 1 | API_KEY=your_ringcentral_client_id 2 | API_SERVER=ringcentral_server 3 | -------------------------------------------------------------------------------- /src/lib/themes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './theme'; 2 | export * from './variables'; 3 | -------------------------------------------------------------------------------- /src/proxy.js: -------------------------------------------------------------------------------- 1 | console.error('The file is deprecated, please upgrade to latest version'); -------------------------------------------------------------------------------- /getBrandConfig/atos/theme.scss: -------------------------------------------------------------------------------- 1 | $header-logo-width: 84px; 2 | $header-logo-height: 24px; 3 | -------------------------------------------------------------------------------- /getBrandConfig/avaya/theme.scss: -------------------------------------------------------------------------------- 1 | $header-logo-width: 108px; 2 | $header-logo-height: 18px; 3 | -------------------------------------------------------------------------------- /src/components/RcVideoScheduleButton/index.ts: -------------------------------------------------------------------------------- 1 | export * from './RcVideoScheduleButton'; 2 | -------------------------------------------------------------------------------- /getBrandConfig/rainbow/theme.scss: -------------------------------------------------------------------------------- 1 | $header-logo-width: 84px; 2 | $header-logo-height: 24px; 3 | -------------------------------------------------------------------------------- /src/lib/themes/btRich/index.ts: -------------------------------------------------------------------------------- 1 | import btRich from './btRich'; 2 | 3 | export default btRich; 4 | -------------------------------------------------------------------------------- /src/lib/themes/rcBlue/index.ts: -------------------------------------------------------------------------------- 1 | import rcBlue from './rcBlue'; 2 | 3 | export default rcBlue; 4 | -------------------------------------------------------------------------------- /src/lib/themes/rcDark/index.ts: -------------------------------------------------------------------------------- 1 | import rcDark from './rcDark'; 2 | 3 | export default rcDark; 4 | -------------------------------------------------------------------------------- /src/lib/themes/attRich/index.ts: -------------------------------------------------------------------------------- 1 | import attRich from './attRich'; 2 | 3 | export default attRich; 4 | -------------------------------------------------------------------------------- /src/lib/themes/atosRich/index.ts: -------------------------------------------------------------------------------- 1 | import atosRich from './atosRich'; 2 | 3 | export default atosRich; 4 | -------------------------------------------------------------------------------- /src/modules/CallQueues/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CallQueue.interface'; 2 | export * from './CallQueues'; 3 | -------------------------------------------------------------------------------- /src/lib/themes/telusRich/index.ts: -------------------------------------------------------------------------------- 1 | import telusRich from './telusRich'; 2 | 3 | export default telusRich; 4 | -------------------------------------------------------------------------------- /getBrandConfig/rc/theme.scss: -------------------------------------------------------------------------------- 1 | $primary-color: #2559E4; 2 | $header-logo-width: 92px; 3 | $header-logo-height: 14px; 4 | -------------------------------------------------------------------------------- /src/assets/atos/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/ringcentral-embeddable/HEAD/src/assets/atos/icon.png -------------------------------------------------------------------------------- /src/lib/themes/rainbowRich/index.ts: -------------------------------------------------------------------------------- 1 | import rainbowRich from './rainbowRich'; 2 | 3 | export default rainbowRich; 4 | -------------------------------------------------------------------------------- /src/modules/SmsTemplates/index.ts: -------------------------------------------------------------------------------- 1 | export * from './SmsTemplates.interface'; 2 | export * from './SmsTemplates'; 3 | -------------------------------------------------------------------------------- /src/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/ringcentral-embeddable/HEAD/src/assets/images/favicon.ico -------------------------------------------------------------------------------- /src/assets/rainbow/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/ringcentral-embeddable/HEAD/src/assets/rainbow/icon.png -------------------------------------------------------------------------------- /src/components/MeetingItem/i18n/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | host: 'Host', 3 | play: 'Play', 4 | log: 'Log', 5 | }; 6 | -------------------------------------------------------------------------------- /src/lib/themes/rcJupiterBlue/index.ts: -------------------------------------------------------------------------------- 1 | import rcJupiterBlue from './rcJupiterBlue'; 2 | 3 | export default rcJupiterBlue; 4 | -------------------------------------------------------------------------------- /docs/assets/voicemail-drop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/ringcentral-embeddable/HEAD/docs/assets/voicemail-drop.png -------------------------------------------------------------------------------- /src/components/MeetingHomePanel/index.ts: -------------------------------------------------------------------------------- 1 | export { MeetingHomePanel } from './MeetingHomePanel'; 2 | export * from './interface'; 3 | -------------------------------------------------------------------------------- /src/lib/themes/avayaCustomized/index.ts: -------------------------------------------------------------------------------- 1 | import avayaCustomized from './avayaCustomized'; 2 | 3 | export default avayaCustomized; 4 | -------------------------------------------------------------------------------- /src/lib/themes/rcHighContrast/index.ts: -------------------------------------------------------------------------------- 1 | import rcHighContrast from './rcHighContrast'; 2 | 3 | export default rcHighContrast; 4 | -------------------------------------------------------------------------------- /src/modules/CallQueuePresence/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CallQueuePresence.interface'; 2 | export * from './CallQueuePresence'; 3 | -------------------------------------------------------------------------------- /docs/assets/call-widget-apps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/ringcentral-embeddable/HEAD/docs/assets/call-widget-apps.png -------------------------------------------------------------------------------- /src/components/MeetingInviteModal/i18n/en-US.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | meetingAdded: "Meeting added", 3 | cancel: "Cancel", 4 | }; 5 | -------------------------------------------------------------------------------- /getBrandConfig/theme.scss: -------------------------------------------------------------------------------- 1 | $header-height: 46px; 2 | @import '~@ringcentral-integration/widgets/lib/commonStyles/colors-variable-overwrite'; 3 | -------------------------------------------------------------------------------- /src/components/ActionMenu/index.tsx: -------------------------------------------------------------------------------- 1 | import { ActionMenu } from '@ringcentral-integration/jsonschema-page'; 2 | 3 | export { ActionMenu }; 4 | -------------------------------------------------------------------------------- /src/lib/isFirefox.js: -------------------------------------------------------------------------------- 1 | export function isFirefox() { 2 | return window.navigator.userAgent.toLowerCase().indexOf('firefox') > -1; 3 | } 4 | -------------------------------------------------------------------------------- /docs/assets/call-widget-auth-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/ringcentral-embeddable/HEAD/docs/assets/call-widget-auth-button.png -------------------------------------------------------------------------------- /docs/assets/call-widget-case-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/ringcentral-embeddable/HEAD/docs/assets/call-widget-case-demo.png -------------------------------------------------------------------------------- /docs/assets/embeddable-3-side-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/ringcentral-embeddable/HEAD/docs/assets/embeddable-3-side-panel.png -------------------------------------------------------------------------------- /docs/assets/voicemail-drop-setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/ringcentral-embeddable/HEAD/docs/assets/voicemail-drop-setting.png -------------------------------------------------------------------------------- /docs/assets/call-widget-contact-notes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/ringcentral-embeddable/HEAD/docs/assets/call-widget-contact-notes.png -------------------------------------------------------------------------------- /docs/assets/call-widget-action-buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/ringcentral-embeddable/HEAD/docs/assets/call-widget-action-buttons.png -------------------------------------------------------------------------------- /src/components/MeetingScheduleButton/index.ts: -------------------------------------------------------------------------------- 1 | import { MeetingScheduleButton } from './MeetingScheduleButton'; 2 | 3 | export default MeetingScheduleButton; 4 | -------------------------------------------------------------------------------- /src/components/MeetingItem/i18n/index.js: -------------------------------------------------------------------------------- 1 | import I18n from '@ringcentral-integration/i18n'; 2 | import loadLocale from './loadLocale'; 3 | 4 | export default new I18n(loadLocale); 5 | -------------------------------------------------------------------------------- /src/components/MainViewPanel/i18n/index.js: -------------------------------------------------------------------------------- 1 | import I18n from '@ringcentral-integration/i18n'; 2 | import loadLocale from './loadLocale'; 3 | 4 | export default new I18n(loadLocale); 5 | -------------------------------------------------------------------------------- /src/components/MeetingHomePanel/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import I18n from '@ringcentral-integration/i18n'; 2 | import loadLocale from './loadLocale'; 3 | 4 | export default new I18n(loadLocale); 5 | -------------------------------------------------------------------------------- /src/containers/MeetingTabContainer/i18n/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | home: 'Home', 3 | schedule: 'Schedule', 4 | recent: 'Past meetings', 5 | recordings: 'Recordings', 6 | }; 7 | -------------------------------------------------------------------------------- /src/components/MeetingHistoryPanel/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import I18n from '@ringcentral-integration/i18n'; 2 | import loadLocale from './loadLocale'; 3 | 4 | export default new I18n(loadLocale); 5 | -------------------------------------------------------------------------------- /src/components/MeetingInviteModal/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import I18n from '@ringcentral-integration/i18n'; 2 | import loadLocale from './loadLocale'; 3 | 4 | export default new I18n(loadLocale); 5 | -------------------------------------------------------------------------------- /src/components/UpcomingMeetingList/i18n/index.js: -------------------------------------------------------------------------------- 1 | import I18n from '@ringcentral-integration/i18n'; 2 | import loadLocale from './loadLocale'; 3 | 4 | export default new I18n(loadLocale); 5 | -------------------------------------------------------------------------------- /src/containers/MeetingTabContainer/i18n/index.js: -------------------------------------------------------------------------------- 1 | import I18n from '@ringcentral-integration/i18n'; 2 | import loadLocale from './loadLocale'; 3 | 4 | export default new I18n(loadLocale); 5 | -------------------------------------------------------------------------------- /src/redirect.js: -------------------------------------------------------------------------------- 1 | import RedirectController from './lib/RedirectController'; 2 | 3 | const prefix = process.env.PREFIX; 4 | 5 | export default new RedirectController({ 6 | prefix, 7 | }); 8 | -------------------------------------------------------------------------------- /test/steps/embeddable.js: -------------------------------------------------------------------------------- 1 | export async function visitThirdPartyPage() { 2 | await page.goto(__THIRD_PARTY_URI__, { 3 | waituntil: 'networkidle0', 4 | timeout: 150000, 5 | }); 6 | } 7 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | npm run build 4 | 5 | cd release 6 | git checkout gh-pages 7 | git add --all . 8 | git commit -m "released at $(date)" 9 | git push origin gh-pages 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs>=1.1 2 | mkdocs-material 3 | mkdocs-badges 4 | markdown_fenced_code_tabs>=1.0.3 5 | mkdocs-markdown-filter>=0.1.1 6 | mdx_include>=0.4.1 7 | mkdocs-exclude>=1.0.2 8 | mkdocs-with-pdf 9 | -------------------------------------------------------------------------------- /src/components/MeetingHistoryPanel/i18n/en-US.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | all: 'All', 3 | recordings: 'Recordings', 4 | noFound: 'No records found', 5 | search: 'Search', 6 | loading: 'Loading...', 7 | }; 8 | -------------------------------------------------------------------------------- /src/components/UpcomingMeetingList/i18n/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | today: 'Today', 3 | allDay: 'All day', 4 | join: 'Join', 5 | details: 'View meeting details', 6 | copy: 'Copy meeting link', 7 | }; 8 | -------------------------------------------------------------------------------- /src/components/SearchAndFilter/i18n/en-US.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | All: 'All', 3 | Missed: 'Missed', 4 | Inbound: 'Inbound', 5 | Outbound: 'Outbound', 6 | UnLogged: 'Unlogged', 7 | Unread: 'Unread', 8 | }; 9 | -------------------------------------------------------------------------------- /overrides/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block announce %} 4 | Announcing the RingCentral Embeddable 3.0 beta Try it out 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /src/containers/DialerPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import DialerPanel from '../../components/DialerPanel'; 3 | 4 | export default connectModule((phone) => phone.dialerUI)(DialerPanel); 5 | -------------------------------------------------------------------------------- /src/containers/ContactsPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import ContactsView from '../../components/ContactsView'; 3 | 4 | export default connectModule((phone) => phone.contactListUI)(ContactsView); 5 | -------------------------------------------------------------------------------- /.env.test.default: -------------------------------------------------------------------------------- 1 | API_KEY=your_ringcentral_client_id 2 | API_SECRET=your_ringcentral_client_secret 3 | API_SERVER=ringcentral_server 4 | TEST_HOST_URI=http://localhost:8080 5 | TEST_JWT_TOKEN= 6 | TEST_HEADLESS=false 7 | TEST_THIRD_PARTY_URI= 8 | TEST_SMS_RECEIVER_NUMBER= 9 | -------------------------------------------------------------------------------- /src/containers/ComposeTextPage/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | connectModule, 3 | } from '@ringcentral-integration/widgets/lib/phoneContext'; 4 | 5 | import ComposeTextPanel from '../../components/ComposeTextPanel'; 6 | 7 | export default connectModule((phone) => phone.composeTextUI)(ComposeTextPanel); 8 | -------------------------------------------------------------------------------- /src/containers/CallCtrlPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | 3 | import { CallCtrlContainer } from './CallCtrlContainer'; 4 | 5 | export const CallCtrlPage = connectModule((phone) => phone.callControlUI)( 6 | CallCtrlContainer, 7 | ); 8 | -------------------------------------------------------------------------------- /src/containers/CallsListPage/index.js: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import CallsListPanel from '../../components/CallsListPanel'; 3 | 4 | export const CallsListPage = connectModule(({ callsListUI }) => callsListUI)( 5 | CallsListPanel, 6 | ); 7 | -------------------------------------------------------------------------------- /src/containers/AudioSettingsPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import { AudioSettingsPanel } from '../../components/AudioSettingsPanel'; 3 | 4 | export default connectModule((phone) => phone.audioSettingsUI)( 5 | AudioSettingsPanel, 6 | ); 7 | -------------------------------------------------------------------------------- /packages/jsonschema-page/.storybook/manager.js: -------------------------------------------------------------------------------- 1 | import { addons } from '@storybook/manager-api'; 2 | import { create } from '@storybook/theming'; 3 | 4 | addons.setConfig({ 5 | theme: create({ 6 | base: 'light', 7 | brandTitle: 'RingCentral Embeddable - JSON Schema Page', 8 | }), 9 | }); 10 | -------------------------------------------------------------------------------- /src/containers/CallDetailsPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import { CallDetailsPanel } from '../../components/CallDetailsPanel'; 3 | 4 | export const CallDetailsPage = connectModule((phone) => phone.callDetailsUI)( 5 | CallDetailsPanel, 6 | ); 7 | -------------------------------------------------------------------------------- /src/containers/ContactDetailsPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import { ContactDetailsView } from '../../components/ContactDetailsView'; 3 | 4 | export default connectModule((phone) => phone.contactDetailsUI)( 5 | ContactDetailsView, 6 | ); 7 | -------------------------------------------------------------------------------- /src/containers/CallingSettingsPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import { CallingSettingsPanel } from '../../components/CallingSettingsPanel'; 3 | 4 | export default connectModule((phone) => phone.callingSettingsUI)( 5 | CallingSettingsPanel, 6 | ); 7 | -------------------------------------------------------------------------------- /src/containers/LogCallPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import LogCallPanel from '../../components/LogCallPanel'; 3 | 4 | const LogCallPage = connectModule((phone) => phone.logCallUI)( 5 | LogCallPanel, 6 | ); 7 | 8 | export default LogCallPage; 9 | -------------------------------------------------------------------------------- /src/containers/CallQueueSettingsPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import { CallQueueSettingsPanel } from '../../components/CallQueueSettingsPanel'; 3 | 4 | export default connectModule((phone) => phone.callQueueSettingsUI)( 5 | CallQueueSettingsPanel, 6 | ); 7 | -------------------------------------------------------------------------------- /src/containers/SettingsPage/index.js: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import { SettingsPanel } from '../../components/SettingsPanel'; 3 | 4 | const SettingsPage = connectModule((phone) => phone.settingsUI)( 5 | SettingsPanel, 6 | ); 7 | 8 | export default SettingsPage; 9 | -------------------------------------------------------------------------------- /src/containers/MainView/index.js: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | 3 | import { MainViewPanel } from '../../components/MainViewPanel'; 4 | 5 | const MainView = connectModule((phone) => phone.mainViewUI)( 6 | MainViewPanel, 7 | ); 8 | 9 | export default MainView; 10 | -------------------------------------------------------------------------------- /src/containers/MessageDetailsPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import { MessageDetailsPanel } from '../../components/MessageDetailsPanel'; 3 | 4 | export const MessageDetailsPage = connectModule((phone) => phone.messageDetailsUI)( 5 | MessageDetailsPanel, 6 | ); 7 | -------------------------------------------------------------------------------- /src/containers/SideDrawerContainer/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import { SideDrawerView } from './SideDrawerView'; 3 | 4 | const SideDrawerContainer = connectModule( 5 | (phone) => phone.sideDrawerUI, 6 | )(SideDrawerView); 7 | 8 | export { SideDrawerContainer }; 9 | -------------------------------------------------------------------------------- /src/containers/GlipChatPage/index.tsx: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import { GlipChatPanel } from '../../components/GlipChatPanel'; 3 | 4 | export const GlipChatPage = connectModule( 5 | (phone) => phone.glipChatUI, 6 | )(GlipChatPanel); 7 | 8 | export default GlipChatPage; 9 | -------------------------------------------------------------------------------- /src/containers/LogMessagesPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import LogMessagesPanel from '../../components/LogMessagesPanel'; 3 | 4 | const LogMessagesPage = connectModule((phone) => phone.logMessagesUI)( 5 | LogMessagesPanel, 6 | ); 7 | 8 | export default LogMessagesPage; -------------------------------------------------------------------------------- /src/containers/SmartNotesPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import { SmartNotesPanel } from '../../components/SmartNotesPanel'; 3 | 4 | const SmartNotesPage = connectModule( 5 | (phone) => phone.smartNotesUI, 6 | )(SmartNotesPanel); 7 | 8 | export { SmartNotesPage }; 9 | -------------------------------------------------------------------------------- /src/containers/WidgetAppsPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import { WidgetAppsPanel } from '../../components/WidgetAppsPanel'; 3 | 4 | const WidgetAppsPage = connectModule( 5 | (phone) => phone.widgetAppsUI, 6 | )(WidgetAppsPanel); 7 | 8 | export { WidgetAppsPage }; 9 | -------------------------------------------------------------------------------- /src/lib/themes/variables/rc.ts: -------------------------------------------------------------------------------- 1 | import { palette2 } from '@ringcentral/juno'; 2 | 3 | const variable = { 4 | primaryColor: '#2559E4', 5 | c2dArrowColor: '#FF8800', 6 | addMeetingBtnColor: '#FF8800', 7 | addMeetingBtnTextColor: '#FFFFFF', 8 | snow: palette2('neutral', 'b01'), 9 | }; 10 | 11 | export default variable; 12 | -------------------------------------------------------------------------------- /test/helpers/index.js: -------------------------------------------------------------------------------- 1 | export function getNewWindowPromise() { 2 | const newWindowPromise = new Promise(resolve => browser.once('targetcreated', target => resolve(target.page()))); 3 | return newWindowPromise; 4 | } 5 | 6 | export function waitForTimeout(timeout) { 7 | return new Promise(resolve => setTimeout(resolve, timeout)); 8 | } 9 | -------------------------------------------------------------------------------- /src/containers/CustomizedPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import { CustomizedPanel } from '../../components/CustomizedPanel'; 3 | 4 | const CustomizedPage = connectModule((phone) => phone.customizedPageUI)( 5 | CustomizedPanel, 6 | ); 7 | 8 | export default CustomizedPage; 9 | -------------------------------------------------------------------------------- /src/containers/GlipGroupsPage/index.ts: -------------------------------------------------------------------------------- 1 | import { GlipGroupsPanel } from '../../components/GlipGroupsPanel'; 2 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 3 | 4 | export const GlipGroupsPage = connectModule( 5 | (phone) => phone.glipGroupsUI, 6 | )(GlipGroupsPanel); 7 | 8 | export default GlipGroupsPage; 9 | -------------------------------------------------------------------------------- /packages/jsonschema-page/.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import type { Preview } from '@storybook/react-webpack5' 2 | 3 | const preview: Preview = { 4 | parameters: { 5 | controls: { 6 | matchers: { 7 | color: /(background|color)$/i, 8 | date: /Date$/i, 9 | }, 10 | }, 11 | }, 12 | }; 13 | 14 | export default preview; -------------------------------------------------------------------------------- /src/containers/PhoneTabsContainer/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 3 | import { SubTabsView } from '../../components/SubTabsView'; 4 | 5 | const PhoneTabsContainer = connectModule( 6 | (phone) => phone.phoneTabsUI, 7 | )(SubTabsView); 8 | 9 | export { PhoneTabsContainer }; 10 | -------------------------------------------------------------------------------- /src/containers/MeetingHomePage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import { MeetingHomePanel } from '../../components/MeetingHomePanel'; 3 | 4 | const MeetingHomePage = connectModule((phone) => phone.meetingHomeUI)( 5 | MeetingHomePanel, 6 | ); 7 | 8 | export { MeetingHomePage as default }; 9 | -------------------------------------------------------------------------------- /src/containers/IncomingCallContainer/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import { IncomingCallView } from '../../components/IncomingCallView'; 3 | 4 | 5 | const IncomingCallContainer = connectModule((phone) => phone.incomingCallUI)( 6 | IncomingCallView, 7 | ); 8 | 9 | export { IncomingCallContainer }; -------------------------------------------------------------------------------- /src/containers/GenericMeetingPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import { GenericMeetingPanel } from '../../components/GenericMeetingPanel'; 3 | 4 | const GenericMeetingPage = connectModule((phone) => phone.genericMeetingUI)( 5 | GenericMeetingPanel, 6 | ); 7 | 8 | export default GenericMeetingPage; 9 | -------------------------------------------------------------------------------- /src/containers/MeetingHistoryPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import MeetingHistoryPanel from '../../components/MeetingHistoryPanel'; 3 | 4 | const MeetingHistoryPage = connectModule((phone) => phone.meetingHistoryUI)( 5 | MeetingHistoryPanel, 6 | ); 7 | 8 | export { MeetingHistoryPage as default }; 9 | -------------------------------------------------------------------------------- /src/containers/ThemeSettingPage/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | connectModule, 3 | } from '@ringcentral-integration/widgets/lib/phoneContext'; 4 | 5 | import { SettingSection } from '../../components/SettingSection'; 6 | 7 | const ThemeSettingPage = connectModule((phone) => phone.themeSettingUI)( 8 | SettingSection, 9 | ); 10 | 11 | export default ThemeSettingPage; 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | *~ 6 | # Dependency directory 7 | node_modules 8 | **/*/__pycache__ 9 | **/.DS_Store 10 | api.json 11 | release 12 | build 13 | .env 14 | .env.test 15 | extension 16 | src/noise-reduction 17 | site 18 | src/plugins.json 19 | .vscode 20 | 21 | *storybook.log 22 | storybook-static 23 | npm-package 24 | dist 25 | -------------------------------------------------------------------------------- /src/lib/Adapter/messageTypes.js: -------------------------------------------------------------------------------- 1 | import { ObjectMap } from '@ringcentral-integration/core/lib/ObjectMap'; 2 | import baseMessageTypes from '@ringcentral-integration/widgets/lib/AdapterCore/baseMessageTypes'; 3 | 4 | const messageTypes = ObjectMap.prefixKeys([ 5 | ...ObjectMap.keys(baseMessageTypes), 6 | 'syncPresence', 7 | ], 'rc-adapter'); 8 | export default messageTypes; 9 | -------------------------------------------------------------------------------- /src/containers/MeetingInviteModal/index.js: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | 3 | import MeetingInviteModal from '../../components/MeetingInviteModal'; 4 | 5 | const MeetingInviteModalPage = connectModule((phone) => phone.meetingInviteUI)( 6 | MeetingInviteModal, 7 | ); 8 | 9 | export default MeetingInviteModalPage; 10 | -------------------------------------------------------------------------------- /src/containers/RingtoneSettingsPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import { RingtoneSettingsPanel } from '../../components/RingtoneSettingsPanel'; 3 | 4 | const RingtoneSettingsPage = connectModule((phone) => phone.ringtoneSettingsUI)( 5 | RingtoneSettingsPanel, 6 | ); 7 | 8 | export default RingtoneSettingsPage; 9 | -------------------------------------------------------------------------------- /src/containers/ConversationsPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import { ConversationsPanel } from '../../components/ConversationsPanel'; 3 | 4 | const ConversationsPage = connectModule((phone) => phone.conversationsUI)( 5 | ConversationsPanel, 6 | ); 7 | 8 | export { ConversationsPage as default, ConversationsPage }; 9 | -------------------------------------------------------------------------------- /src/containers/OtherDeviceCallCtrlPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import { OtherDeviceCallCtrlPanel } from '../../components/OtherDeviceCallCtrlPanel'; 3 | 4 | const OtherDeviceCallCtrlPage = connectModule( 5 | (phone) => phone.simpleCallControlUI, 6 | )(OtherDeviceCallCtrlPanel); 7 | 8 | export { OtherDeviceCallCtrlPage }; 9 | -------------------------------------------------------------------------------- /src/lib/renderContactName.js: -------------------------------------------------------------------------------- 1 | import { isInbound } from '@ringcentral-integration/commons/lib/callLogHelpers'; 2 | 3 | export function renderContactName(call) { 4 | const matches = isInbound(call) ? call.fromMatches : call.toMatches; 5 | if (matches && matches.length === 1) { 6 | return matches[0].name; 7 | } 8 | return isInbound(call) ? call.from.name : call.to.name; 9 | } 10 | -------------------------------------------------------------------------------- /src/containers/ConversationPage/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | connectModule, 3 | } from '@ringcentral-integration/widgets/lib/phoneContext'; 4 | 5 | import ConversationPanel from '../../components/ConversationPanel'; 6 | 7 | const ConversationPage = connectModule((phone) => phone.conversationUI)( 8 | ConversationPanel, 9 | ); 10 | 11 | export { ConversationPage as default, ConversationPage }; 12 | -------------------------------------------------------------------------------- /src/components/SearchAndFilter/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import I18n from '@ringcentral-integration/i18n'; 2 | 3 | import type enUS from './en-US'; 4 | // @ts-expect-error 5 | import loadLocale from './loadLocale'; 6 | 7 | const i18n = new I18n(loadLocale); 8 | 9 | export const t = i18n.getString.bind(i18n); 10 | 11 | export type I18nKey = keyof typeof enUS; 12 | 13 | export default i18n; 14 | -------------------------------------------------------------------------------- /src/components/SettingsPanel/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import I18n from '@ringcentral-integration/i18n'; 2 | 3 | import type enUS from './en-US'; 4 | // @ts-expect-error 5 | import loadLocale from './loadLocale'; 6 | 7 | const i18n = new I18n(loadLocale); 8 | 9 | export const t = i18n.getString.bind(i18n); 10 | 11 | export type I18nKey = keyof typeof enUS; 12 | 13 | export default i18n; 14 | -------------------------------------------------------------------------------- /src/components/CallCtrlPanel/ActiveCallPad/actions.ts: -------------------------------------------------------------------------------- 1 | export const ACTIONS_CTRL_MAP = { 2 | muteCtrl: 'muteCtrl', 3 | keypadCtrl: 'keypadCtrl', 4 | holdCtrl: 'holdCtrl', 5 | mergeOrAddCtrl: 'mergeOrAddCtrl', 6 | recordCtrl: 'recordCtrl', 7 | transferCtrl: 'transferCtrl', 8 | flipCtrl: 'flipCtrl', 9 | parkCtrl: 'parkCtrl', 10 | completeTransferCtrl: 'completeTransferCtrl', 11 | }; 12 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import I18n from '@ringcentral-integration/i18n'; 2 | 3 | import type enUS from './en-US'; 4 | // @ts-expect-error 5 | import loadLocale from './loadLocale'; 6 | 7 | const i18n = new I18n(loadLocale); 8 | 9 | export const t = i18n.getString.bind(i18n); 10 | 11 | export type I18nKey = keyof typeof enUS; 12 | 13 | export default i18n; 14 | -------------------------------------------------------------------------------- /src/containers/TransferPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import type { TransferUIContainerProps } from '@ringcentral-integration/widgets/modules/TransferUI'; 3 | 4 | import TransferPanel from '../../components/TransferPanel'; 5 | 6 | export default connectModule( 7 | (phone) => phone.transferUI, 8 | )(TransferPanel); -------------------------------------------------------------------------------- /src/containers/ThirdPartySettingSectionPage/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | connectModule, 3 | } from '@ringcentral-integration/widgets/lib/phoneContext'; 4 | 5 | import { SettingSection } from '../../components/SettingSection'; 6 | 7 | const ThirdPartySettingSectionPage = connectModule((phone) => phone.thirdPartySettingSectionUI)( 8 | SettingSection, 9 | ); 10 | 11 | export default ThirdPartySettingSectionPage; 12 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/en-US.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: 'Primary number', 3 | DirectNumber: 'Direct number', 4 | MainCompanyNumber: 'Main company number', 5 | CompanyNumber: 'Company number', 6 | CompanyFaxNumber: 'Fax number', 7 | Blocked: 'Blocked', 8 | AdditionalCompanyNumber: 'Company number', 9 | ForwardedCompanyNumber: 'Forwarded number', 10 | from: 'My caller ID', 11 | }; 12 | -------------------------------------------------------------------------------- /docs/config/user-agent.md: -------------------------------------------------------------------------------- 1 | # Customize X-User-Agent 2 | 3 | We provide default `X-User-Agent` header as `RingCentralEmbeddable/0.2.0 RCJSSDK/3.1.3` in RingCentral API request for SDK usage analysis in backend. In this API, developers can also provide their desired User Agent into widget. 4 | 5 | After that widget will change `X-User-Agent` header into `TestAPP/1.0.0 RingCentralEmbeddable/0.2.0 RCJSSDK/3.1.3` when send request to RingCentral Server. 6 | -------------------------------------------------------------------------------- /src/components/MeetingHomePanel/i18n/en-US.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | noUpcomingMeeting: "No upcoming meetings", 3 | start: "Start", 4 | startDescription: "Start a video meeting instantly", 5 | schedule: "Schedule", 6 | scheduleDescription: "Schedule a meeting for later", 7 | join: "Join", 8 | joinDescription: "Join a meeting you were invited to", 9 | enterMeetingId: "Enter meeting ID or link", 10 | cancel: "Cancel", 11 | }; 12 | -------------------------------------------------------------------------------- /src/lib/patchOpenWindow.js: -------------------------------------------------------------------------------- 1 | try { 2 | if (!window.__originalOpen) { 3 | window.__originalOpen = window.open; 4 | window.open = function(url, id, options) { 5 | if (typeof url === 'string' && url.indexOf('javascript') > -1) { 6 | throw new Error('Invalid open window uri'); 7 | } 8 | return window.__originalOpen(url, id, options); 9 | } 10 | } 11 | } catch (e) { 12 | console.error(e); 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/containers/NotificationContainer/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import type { NotificationContainerProps } from '@ringcentral-integration/widgets/containers/NotificationContainer/NotificationContainer.interface'; 3 | import { NotificationPanel } from '../../components/NotificationPanel'; 4 | 5 | export const NotificationContainer = connectModule< 6 | any, 7 | NotificationContainerProps 8 | >((phone) => phone.alertUI)(NotificationPanel); 9 | -------------------------------------------------------------------------------- /docs/self-hosting.md: -------------------------------------------------------------------------------- 1 | # Hosting RingCentral Embeddable from your own servers 2 | 3 | While not recommended, some developers may wish to download the RingCentral Embeddable javascript library and host it from their own servers. If you elect to do this, make sure you also: 4 | 5 | * create a custom `redirect.html` page 6 | * host your `redirect.html` file from the same domain as your Embeddable javascript file 7 | * [update your redirect Uri](config/redirect-uri.md) to point to your custom `redirect.html` file 8 | -------------------------------------------------------------------------------- /src/lib/themes/variables/telus.ts: -------------------------------------------------------------------------------- 1 | const variable = { 2 | primaryColor: '#57a708', 3 | primaryColorHighlight: 'rgba(87, 167, 8, 0.4)', 4 | primaryColorHighlightSolid: 'rgb(188, 220, 156)', 5 | callBtnColor: '#57a708', 6 | smsBubbleBackgroundColor: '#ecffd9', 7 | brandFontColor: '#49166d', 8 | brandFontColorHighlight: 'rgba(73,22,109, 0.4)', 9 | c2dArrowColor: '#66cc00', 10 | addMeetingBtnColor: '#2b8000', 11 | addMeetingBtnTextColor: '#ffffff', 12 | }; 13 | 14 | export default variable; 15 | -------------------------------------------------------------------------------- /src/lib/themes/variables/att.ts: -------------------------------------------------------------------------------- 1 | const variable = { 2 | primaryColor: '#067ab4', 3 | primaryColorHighlight: 'rgba(6, 122, 180, 0.4)', 4 | primaryColorHighlightSolid: 'rgb(155, 202, 225)', 5 | callBtnColor: '#5fb95c', 6 | smsBubbleBackgroundColor: '#d5f3fd', 7 | brandFontColor: '#067ab4', 8 | brandFontColorHighlight: 'rgba(6, 122, 180, 0.4)', 9 | 10 | c2dArrowColor: '#ffffff', 11 | addMeetingBtnColor: '#067ab4', 12 | addMeetingBtnTextColor: '#FFFFFF', 13 | }; 14 | 15 | export default variable; 16 | -------------------------------------------------------------------------------- /src/lib/themes/variables/bt.ts: -------------------------------------------------------------------------------- 1 | const variable = { 2 | primaryColor: '#5514B4', 3 | primaryColorHighlight: 'rgba(0, 82, 142, 0.4)', 4 | primaryColorHighlightSolid: 'rgb(153, 186, 210)', 5 | callBtnColor: '#5fb95c', 6 | smsBubbleBackgroundColor: '#e5edf3', 7 | brandFontColor: '#5514B4', 8 | brandFontColorHighlight: 'rgba(0, 82, 142, 0.4)', 9 | 10 | c2dArrowColor: '#5514B4', 11 | addMeetingBtnColor: '#5514B4', 12 | addMeetingBtnTextColor: '#FFFFFF', 13 | }; 14 | 15 | export default variable; 16 | -------------------------------------------------------------------------------- /packages/jsonschema-page/src/Fields/index.ts: -------------------------------------------------------------------------------- 1 | import { RegistryFieldsType } from '@rjsf/utils'; 2 | 3 | import { Alert } from './Alert'; 4 | import { Typography } from './Typography'; 5 | import { Button } from './Button'; 6 | import { List } from './List'; 7 | import { Link } from './Link'; 8 | import { Search } from './Search'; 9 | 10 | export const fields: RegistryFieldsType = { 11 | admonition: Alert, 12 | typography: Typography, 13 | button: Button, 14 | list: List, 15 | link: Link, 16 | search: Search, 17 | }; 18 | -------------------------------------------------------------------------------- /src/components/ThirdPartyContactSourceIcon/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { styled } from '@ringcentral/juno'; 3 | 4 | const StyledImage = styled.img` 5 | padding: 0; 6 | border: 0; 7 | line-height: 1; 8 | `; 9 | 10 | export default function ThirdPartyContactSourceIcon({ 11 | iconUri, 12 | sourceName, 13 | className = undefined, 14 | width = '100%', 15 | height = '100%', 16 | }) { 17 | return ( 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/modules/Adapter/actionTypes.js: -------------------------------------------------------------------------------- 1 | import { 2 | moduleActionTypes, 3 | } from '@ringcentral-integration/commons/enums/moduleActionTypes'; 4 | import { ObjectMap } from '@ringcentral-integration/core/lib/ObjectMap'; 5 | 6 | export const actionTypes = ObjectMap.prefixKeys([ 7 | ...ObjectMap.keys(moduleActionTypes), 8 | 'syncClosed', 9 | 'syncMinimized', 10 | 'syncSize', 11 | 'syncFocus', 12 | 'syncPosition', 13 | 'showAdapter', 14 | 'setClickToDial', 15 | 'setShowDemoWarning', 16 | ], 'rc-adapter'); 17 | 18 | export default actionTypes; 19 | -------------------------------------------------------------------------------- /src/lib/hackSend.js: -------------------------------------------------------------------------------- 1 | export default function hackSend(sdk) { 2 | const platform = sdk.platform(); 3 | platform.originalSend = platform.send; 4 | const newSendFunc = (options) => { 5 | let { headers } = options; 6 | headers = { 7 | ...headers, 8 | 'Cache-Control': 'private, no-cache, no-store, must-revalidate', 9 | Pragma: 'no-cache', 10 | Expires: '-1' 11 | }; 12 | return platform.originalSend({ 13 | ...options, 14 | headers, 15 | }); 16 | }; 17 | platform.send = newSendFunc; 18 | return sdk; 19 | } 20 | -------------------------------------------------------------------------------- /jest-puppeteer.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | server: { 3 | command: 'ON_TEST=1 NODE_ENV=development yarn start', // use development babel setting 4 | port: 8080, 5 | launchTimeout: 100000 6 | }, 7 | launch: { 8 | product: 'chrome', 9 | dumpio: true, 10 | ignoreHTTPSErrors: true, 11 | args: ['--use-fake-ui-for-media-stream', '--use-fake-device-for-media-stream', '--disable-features=IsolateOrigins,site-per-process', '--no-sandbox'], 12 | headless: process.env.TEST_HEADLESS === 'true' 13 | }, 14 | browserContext: 'incognito', 15 | }; 16 | -------------------------------------------------------------------------------- /src/assets/images/feedback.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/containers/ConferenceCallDialerPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connectModule } from '@ringcentral-integration/widgets/lib/phoneContext'; 2 | import type { ConferenceDialerUIContainerProps } from '@ringcentral-integration/widgets/modules/ConferenceDialerUI/ConferenceDialerUI.interface'; 3 | import { ConferenceDialerPanel } from '../../components/ConferenceDialerPanel'; 4 | 5 | export const ConferenceCallDialerPage = connectModule< 6 | any, 7 | ConferenceDialerUIContainerProps 8 | >((phone) => phone.conferenceDialerUI)(ConferenceDialerPanel); 9 | 10 | export default ConferenceCallDialerPage; 11 | -------------------------------------------------------------------------------- /src/lib/themes/variables/atos.ts: -------------------------------------------------------------------------------- 1 | import theme from '../atosRich'; 2 | 3 | const primaryColor = theme.palette.nav.menuBg; 4 | 5 | const variable = { 6 | primaryColor, 7 | primaryColorHighlight: 'rgba(0, 102, 161,0.4)', 8 | primaryColorHighlightSolid: '#147ab5', 9 | callBtnColor: '#3C9949', 10 | c2dArrowColor: '#ffffff', 11 | addMeetingBtnColor: primaryColor, 12 | addMeetingBtnTextColor: '#FFFFFF', 13 | brandFontColor: primaryColor, 14 | brandFontColorHighlight: 'rgba(0, 102, 161,0.4)', 15 | smsBubbleBackgroundColor: '#F3F3F3', 16 | }; 17 | 18 | export default variable; 19 | -------------------------------------------------------------------------------- /getBrandConfig/att/theme.scss: -------------------------------------------------------------------------------- 1 | $primary-color: #067ab4; 2 | $primary-color-highlight: rgba(6, 122, 180, 0.4); 3 | $primary-color-highlight-solid: rgb(155, 202, 225); 4 | 5 | $dialer-btn-border-color: $primary-color-highlight; 6 | $call-btn-color: #5fb95c; 7 | 8 | $sms-bubble-background-color: #d5f3fd; 9 | $brand-font-color: $primary-color; 10 | $brand-font-color-highlight: $primary-color-highlight; 11 | 12 | $c2d-arrow-color: #ffffff; 13 | 14 | $add-meeting-btn-color: $primary-color; 15 | $add-meeting-btn-text-color: #ffffff; 16 | 17 | $header-logo-width: 84px; 18 | $header-logo-height: 18px; 19 | -------------------------------------------------------------------------------- /getBrandConfig/bt/theme.scss: -------------------------------------------------------------------------------- 1 | $primary-color: #5514B4; 2 | $primary-color-highlight: rgba(0, 82, 142, 0.4); 3 | $primary-color-highlight-solid: rgb(153, 186, 210); 4 | 5 | $dialer-btn-border-color: $primary-color-highlight; 6 | $call-btn-color: #5fb95c; 7 | 8 | $sms-bubble-background-color: #e5edf3; 9 | $brand-font-color: $primary-color; 10 | $brand-font-color-highlight: $primary-color-highlight; 11 | 12 | $c2d-arrow-color: #5514B4; 13 | 14 | $add-meeting-btn-color: $primary-color; 15 | $add-meeting-btn-text-color: #FFFFFF; 16 | 17 | $header-logo-width: 108px; 18 | $header-logo-height: 18px; 19 | -------------------------------------------------------------------------------- /getBrandConfig/telus/theme.scss: -------------------------------------------------------------------------------- 1 | $primary-color: #57a708; 2 | $primary-color-highlight: rgba(87, 167, 8, 0.4); 3 | $primary-color-highlight-solid: rgb(188, 220, 156); 4 | 5 | $dialer-btn-border-color: $primary-color-highlight; 6 | $call-btn-color: $primary-color; 7 | 8 | $sms-bubble-background-color: #ecffd9; 9 | $brand-font-color: #49166d; 10 | $brand-font-color-highlight: rgba(73,22,109, 0.4); 11 | 12 | $c2d-arrow-color: #66cc00; 13 | 14 | $add-meeting-btn-color: $primary-color; 15 | $add-meeting-btn-text-color: #FFFFFF; 16 | 17 | $header-logo-width: 84px; 18 | $header-logo-height: 18px; 19 | -------------------------------------------------------------------------------- /src/modules/GlipPersons/index.js: -------------------------------------------------------------------------------- 1 | import GlipPersons from '@ringcentral-integration/commons/modules/GlipPersons'; 2 | import { Module } from '@ringcentral-integration/commons/lib/di'; 3 | 4 | @Module({ 5 | deps: [ 6 | 'AppFeatures', 7 | ], 8 | }) 9 | export default class NewGlipPersons extends GlipPersons { 10 | constructor({ appFeatures, ...options }) { 11 | super(options); 12 | this._appFeatures = appFeatures; 13 | } 14 | 15 | // TODO: update permission check in widgets lib 16 | get _hasPermission() { 17 | return !!this._appFeatures.hasGlipPermission; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/lib/parseUri.js: -------------------------------------------------------------------------------- 1 | import url from 'url'; 2 | import qs from 'qs'; 3 | 4 | export default function parseCallbackUri(callbackUri) { 5 | const { query, hash } = url.parse(callbackUri, true); 6 | const hashObject = hash ? qs.parse(hash.replace(/^#/, '')) : {}; 7 | if (query.error) { 8 | const error = new Error(query.error); 9 | for (const key in query) { 10 | if (query::Object.prototype.hasOwnProperty(key)) { 11 | error[key] = query[key]; 12 | } 13 | } 14 | throw error; 15 | } 16 | 17 | return { 18 | ...query, 19 | ...hashObject, 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/themes/variables/avaya.ts: -------------------------------------------------------------------------------- 1 | import theme from '../avayaCustomized'; 2 | 3 | const primaryColor = theme.palette.nav.menuBg; 4 | 5 | const variable = { 6 | primaryColor, 7 | primaryColorHighlight: 'rgba(87,87,89,0.4)', 8 | primaryColorHighlightSolid: 'rgb(187,187,188)', 9 | callBtnColor: '#3C9949', 10 | c2dArrowColor: '#ffffff', 11 | addMeetingBtnColor: primaryColor, 12 | addMeetingBtnTextColor: '#FFFFFF', 13 | brandFontColor: primaryColor, 14 | brandFontColorHighlight: 'rgba(87,87,89,0.4)', 15 | smsBubbleBackgroundColor: '#F3F3F3', 16 | }; 17 | 18 | export default variable; 19 | -------------------------------------------------------------------------------- /src/lib/themes/variables/rainbow.ts: -------------------------------------------------------------------------------- 1 | import theme from '../rainbowRich'; 2 | 3 | const primaryColor = theme.palette.nav.menuBg; 4 | 5 | const variable = { 6 | primaryColor, 7 | primaryColorHighlight: 'rgba(107, 72, 157, 0.4)', 8 | primaryColorHighlightSolid: '#7f5cb1', 9 | callBtnColor: '#3C9949', 10 | c2dArrowColor: '#ffffff', 11 | addMeetingBtnColor: primaryColor, 12 | addMeetingBtnTextColor: '#FFFFFF', 13 | brandFontColor: primaryColor, 14 | brandFontColorHighlight: 'rgba(107, 72, 157, 0.4)', 15 | smsBubbleBackgroundColor: '#F3F3F3', 16 | }; 17 | 18 | export default variable; 19 | -------------------------------------------------------------------------------- /docs/config/badge.md: -------------------------------------------------------------------------------- 1 | # Embeddable badge 2 | 3 | In adapter JS way, our codes will generate a `RingCentral Badge` in web page by default: 4 | 5 | ![Old UI](https://user-images.githubusercontent.com/7036536/55137185-47d4f500-516b-11e9-9519-17ded87f338c.jpeg) 6 | 7 | In latest version, we implement a new dock UI: 8 | 9 | ![New dock UI](https://user-images.githubusercontent.com/7036536/55137190-4d323f80-516b-11e9-9b90-aa285a1147dd.jpeg) 10 | 11 | ## Changing the location of the badge 12 | 13 | Use the `defaultDirection` configuration parameter to dock the badge either the `left` or `right` of the window. 14 | -------------------------------------------------------------------------------- /src/lib/contactMatchHelper.js: -------------------------------------------------------------------------------- 1 | import callDirections from '@ringcentral-integration/commons/enums/callDirections'; 2 | 3 | export function getWebphoneSessionContactMatch(session, contactMapping) { 4 | if (session.contactMatch) { 5 | return session.contactMatch; 6 | } 7 | const fromMatches = 8 | (contactMapping && contactMapping[session.from]) || []; 9 | const toMatches = 10 | (contactMapping && contactMapping[session.to]) || []; 11 | const nameMatches = 12 | session.direction === callDirections.outbound 13 | ? toMatches 14 | : fromMatches; 15 | return nameMatches[0]; 16 | } 17 | -------------------------------------------------------------------------------- /packages/jsonschema-page/src/Templates/FieldHelpTemplate.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { RcFormHelperText } from '@ringcentral/juno'; 3 | import { helpId, FieldHelpProps, FormContextType, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils'; 4 | 5 | export default function FieldHelpTemplate< 6 | T = any, 7 | S extends StrictRJSFSchema = RJSFSchema, 8 | F extends FormContextType = any 9 | >(props: FieldHelpProps) { 10 | const { idSchema, help } = props; 11 | if (!help) { 12 | return null; 13 | } 14 | const id = helpId(idSchema); 15 | return {help}; 16 | } -------------------------------------------------------------------------------- /src/lib/themes/variables/index.ts: -------------------------------------------------------------------------------- 1 | import atosVariable from './atos'; 2 | import attVariable from './att'; 3 | import avayaVariable from './avaya'; 4 | import btVariable from './bt'; 5 | import rainbowVariable from './rainbow'; 6 | import rcVariable from './rc'; 7 | import telusVariable from './telus'; 8 | 9 | const mapping = { 10 | rc: rcVariable, 11 | att: attVariable, 12 | bt: btVariable, 13 | telus: telusVariable, 14 | avaya: avayaVariable, 15 | atos: atosVariable, 16 | rainbow: rainbowVariable, 17 | }; 18 | 19 | export const getBrandVariable = (brand = 'rc') => { 20 | return mapping[brand] || mapping.rc; 21 | }; 22 | -------------------------------------------------------------------------------- /packages/jsonschema-page/src/Fields/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { 4 | RcButton, 5 | } from '@ringcentral/juno'; 6 | 7 | export function Button({ 8 | schema, 9 | uiSchema = {}, 10 | onFocus, 11 | disabled, 12 | name, 13 | }) { 14 | return ( 15 | { 18 | onFocus(name, '$$clicked'); 19 | }} 20 | fullWidth={uiSchema['ui:fullWidth']} 21 | disabled={disabled} 22 | color={uiSchema['ui:color'] || 'primary'} 23 | > 24 | {schema.title} 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/modules/MessageSender/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@ringcentral-integration/commons/lib/di'; 2 | 3 | import { 4 | MessageSender as MessageSenderBase, 5 | messageSenderStatus, 6 | } from '@ringcentral-integration/commons/modules/MessageSender'; 7 | 8 | @Module({ 9 | name: 'MessageSender', 10 | deps: [], 11 | }) 12 | export class MessageSender extends MessageSenderBase { 13 | // TODO: fix noAttachmentToExtension error not idle issue 14 | async _validateToNumbers(options) { 15 | const result = await super._validateToNumbers(options); 16 | this.setSendStatus(messageSenderStatus.idle); 17 | return result; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/modules/NumberValidate/index.ts: -------------------------------------------------------------------------------- 1 | import { NumberValidate as NumberValidateBase } from '@ringcentral-integration/commons/modules/NumberValidate'; 2 | import { Module } from '@ringcentral-integration/commons/lib/di'; 3 | 4 | @Module({ 5 | name: 'NewNumberValidate', 6 | deps: [] 7 | }) 8 | export class NumberValidate extends NumberValidateBase { 9 | override handleExtension(resultItem) { 10 | if (resultItem.dialingDetails?.serviceCode?.dialing) { 11 | return { 12 | isAnExtension: false, 13 | parsedNumber: resultItem.originalString, 14 | }; 15 | } 16 | return super.handleExtension(resultItem); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/lib/callHelper.ts: -------------------------------------------------------------------------------- 1 | import callDirections from '@ringcentral-integration/commons/enums/callDirections'; 2 | 3 | export function getCallContact(call) { 4 | if (!call) { 5 | return null; 6 | } 7 | const matchKey = call.direction === callDirections.inbound ? 'fromMatches' : 'toMatches'; 8 | const numberKey = call.direction === callDirections.inbound ? 'from' : 'to'; 9 | const matches = call[matchKey] || []; 10 | if (matches.length === 1) { 11 | return matches[0]; 12 | } 13 | if (!call[numberKey]) { 14 | return null; 15 | } 16 | return { 17 | phoneNumber: call[numberKey].phoneNumber || call[numberKey].extensionNumber, 18 | }; 19 | } -------------------------------------------------------------------------------- /packages/jsonschema-page/src/Fields/Alert.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { 4 | RcAlert, 5 | styled, 6 | } from '@ringcentral/juno'; 7 | import { TextWithMarkdown } from '../components/TextWithMarkdown'; 8 | 9 | const StyledAlert = styled(RcAlert)` 10 | &.RcAlert-root { 11 | padding: 10px; 12 | } 13 | 14 | .RcAlert-message { 15 | font-size: 0.875rem; 16 | } 17 | `; 18 | 19 | export function Alert({ 20 | schema, 21 | uiSchema, 22 | }) { 23 | return ( 24 | 25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/modules/CallQueuePresence/CallQueuePresence.interface.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataFetcherV2ConsumerBaseDeps, 3 | DataSourceBaseProps, 4 | } from '@ringcentral-integration/commons/modules/DataFetcherV2'; 5 | 6 | import { DataFetcherV2 } from '@ringcentral-integration/commons/modules/DataFetcherV2'; 7 | import { AppFeatures } from '../AppFeatures'; 8 | 9 | export interface CallQueuesPresenceOptions extends DataSourceBaseProps {} 10 | 11 | export interface Deps extends DataFetcherV2ConsumerBaseDeps { 12 | client: any; 13 | callQueuePresenceOptions?: CallQueuesPresenceOptions; 14 | extensionFeatures: AppFeatures; 15 | dateFetcherV2: DataFetcherV2; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/modules/CallQueues/CallQueue.interface.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataFetcherV2ConsumerBaseDeps, 3 | DataSourceBaseProps, 4 | } from '@ringcentral-integration/commons/modules/DataFetcherV2'; 5 | 6 | import { ExtensionFeatures } from '@ringcentral-integration/commons/modules/ExtensionFeatures'; 7 | import { DataFetcherV2 } from '@ringcentral-integration/commons/modules/DataFetcherV2'; 8 | 9 | export interface CallQueuesOptions extends DataSourceBaseProps {} 10 | 11 | export interface Deps extends DataFetcherV2ConsumerBaseDeps { 12 | client: any; 13 | callQueuesOptions?: CallQueuesOptions; 14 | extensionFeatures: ExtensionFeatures; 15 | dateFetcherV2: DataFetcherV2; 16 | } 17 | -------------------------------------------------------------------------------- /src/components/DialerPanel/StyledRecipientsInput.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@ringcentral/juno'; 2 | 3 | import RecipientsInput from '../RecipientsInput'; 4 | 5 | export const StyledRecipientsInput = styled(RecipientsInput)` 6 | display: flex; 7 | flex-direction: row; 8 | justify-content: center; 9 | margin-top: 0; 10 | margin-bottom: 0; 11 | 12 | label { 13 | display: none; 14 | } 15 | 16 | input { 17 | text-align: center; 18 | } 19 | 20 | .RecipientsInput_rightPanel { 21 | display: flex; 22 | flex-direction: row; 23 | justify-content: center; 24 | } 25 | 26 | .RecipientsInput_inputField { 27 | padding-left: 20px; 28 | } 29 | `; 30 | -------------------------------------------------------------------------------- /src/components/MainViewPanel/i18n/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | dialpadLabel: 'Dialpad', 3 | phoneLabel: 'Phone', 4 | callsLabel: 'Calls', 5 | historyLabel: 'History', 6 | recordingsLabel: 'Recordings', 7 | messagesLabel: 'Messages', 8 | textLabel: 'Text', 9 | faxLabel: 'Fax', 10 | voicemailLabel: 'Voicemail', 11 | inboxLabel: 'Inbox', 12 | moreMenuLabel: 'More', 13 | contactsLabel: 'Contacts', 14 | meetingLabel: 'Meetings', 15 | conferenceLabel: 'Schedule Conference', 16 | hangoutsLabel: 'Start Hangouts', 17 | hangoutsTitle: 'Start Hangouts with Conferencing', 18 | settingsLabel: 'Settings', 19 | glipLabel: 'Chat', 20 | composeText: 'Compose Text', 21 | }; 22 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | require('dotenv').config({ 4 | path: path.resolve(__dirname, '.env.test'), 5 | }); 6 | 7 | module.exports = { 8 | setupFiles: [ 9 | '/test/setup.js' 10 | ], 11 | setupFilesAfterEnv: ['/jest.setup.js'], 12 | preset: 'jest-puppeteer', 13 | globals: { 14 | __HOST_URI__: process.env.TEST_HOST_URI, 15 | __JWT_TOKEN__: process.env.TEST_JWT_TOKEN, 16 | __THIRD_PARTY_URI__: process.env.TEST_THIRD_PARTY_URI, 17 | __TEST_SMS_RECEIVER_NUMBER__: process.env.TEST_SMS_RECEIVER_NUMBER, 18 | }, 19 | testMatch: [ 20 | '**/test/**/*.test.js' 21 | ], 22 | reporters: ['default'], 23 | maxConcurrency: 1, 24 | }; 25 | -------------------------------------------------------------------------------- /src/proxy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Proxy 14 | 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/integration/analytics.md: -------------------------------------------------------------------------------- 1 | # Enable analytics 2 | 3 | Developers can implement their own custom event tracking with internal or third-party analytics systems using Embeddable's API. This feature is disabled by default. To enable analytics tracking, enable the [`enableAnalytics`](../config/index.md) configuration parameter. 4 | 5 | ## Listen for the track event 6 | 7 | ```js 8 | window.addEventListener('message', (e) => { 9 | const data = e.data; 10 | if (data) { 11 | switch (data.type) { 12 | case 'rc-analytics-track': 13 | // get analytics data 14 | console.log('rc-analytics-track:', data.event, data.properties); 15 | break; 16 | default: 17 | break; 18 | } 19 | } 20 | }); 21 | ``` 22 | -------------------------------------------------------------------------------- /src/components/NavigationBar/TabIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { styled } from '@ringcentral/juno/foundation'; 4 | import { RcIcon } from '@ringcentral/juno'; 5 | 6 | const ImageIcon = styled.img` 7 | width: 24px; 8 | height: 24px; 9 | `; 10 | export function TabIcon({ icon, activeIcon, iconUri, activeIconUri, active, alt }) { 11 | if (icon) { 12 | const color = active ? 'nav.iconSelected' : 'nav.iconDefault'; 13 | return ( 14 | 19 | ); 20 | } 21 | return ( 22 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /packages/jsonschema-page/src/Templates/AddButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { RcIconButton } from '@ringcentral/juno'; 3 | import { FormContextType, IconButtonProps, RJSFSchema, StrictRJSFSchema, TranslatableString } from '@rjsf/utils'; 4 | import { Add } from '@ringcentral/juno-icon'; 5 | 6 | export default function AddButton({ 7 | uiSchema, 8 | registry, 9 | ...props 10 | }: IconButtonProps) { 11 | const { translateString } = registry; 12 | return ( 13 | 19 | ); 20 | } -------------------------------------------------------------------------------- /src/modules/SimpleCallControlUI/index.ts: -------------------------------------------------------------------------------- 1 | import { SimpleCallControlUI as SimpleCallControlUIBase } from '@ringcentral-integration/widgets/modules/SimpleCallControlUI'; 2 | import { Module } from '@ringcentral-integration/commons/lib/di'; 3 | 4 | @Module({ 5 | name: 'NewSimpleCallControlUI', 6 | deps: [], 7 | }) 8 | export class SimpleCallControlUI extends SimpleCallControlUIBase { 9 | getUIFunctions(options) { 10 | const functions = super.getUIFunctions(options); 11 | return { 12 | ...functions, 13 | updateSessionMatchedContact: (telephonySessionId, contact) => { 14 | this._deps.contactMatcher.setCallMatched({ 15 | telephonySessionId, 16 | toEntityId: contact.id 17 | }); 18 | } 19 | }; 20 | } 21 | } -------------------------------------------------------------------------------- /getBrandConfig/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const configs = {}; 5 | const configFolders = fs.readdirSync(__dirname); 6 | const brandConfigs = {}; 7 | configFolders.forEach((folderName) => { 8 | const fullPath = path.resolve(__dirname, folderName); 9 | if (!fs.statSync(fullPath).isDirectory()) { 10 | return; 11 | } 12 | // disable-eslint 13 | const config = require(path.join(__dirname, folderName)); 14 | configs[folderName] = config; 15 | brandConfigs[folderName] = config.brandConfig; 16 | }); 17 | 18 | const getBrandConfig = (brand) => { 19 | return configs[brand]; 20 | }; 21 | 22 | exports.getBrandConfig = getBrandConfig; 23 | exports.brandConfigs = brandConfigs; 24 | exports.supportedBrands = Object.keys(brandConfigs); 25 | -------------------------------------------------------------------------------- /src/components/SubTabsView/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { styled } from '@ringcentral/juno/foundation'; 3 | import { SubTabs } from '../SubTabs'; 4 | 5 | const Container = styled.div` 6 | display: flex; 7 | flex-direction: column; 8 | width: 100%; 9 | height: 100%; 10 | `; 11 | 12 | const Content = styled.div` 13 | flex: 1; 14 | overflow: hidden; 15 | `; 16 | 17 | export function SubTabsView({ 18 | currentPath, 19 | goTo, 20 | children, 21 | tabs, 22 | variant, 23 | }) { 24 | return ( 25 | 26 | 32 | 33 | {children} 34 | 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /packages/jsonschema-page/src/Widgets/index.tsx: -------------------------------------------------------------------------------- 1 | import { FormContextType, RegistryWidgetsType, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils'; 2 | 3 | import CheckboxWidget from './CheckboxWidget'; 4 | import CheckboxesWidget from './CheckboxesWidget'; 5 | import RadioWidget from './RadioWidget'; 6 | import RangeWidget from './RangeWidget'; 7 | import SelectWidget from './SelectWidget'; 8 | import TextareaWidget from './TextareaWidget'; 9 | import FileWidget from './FileWidget'; 10 | import AutocompleteWidget from './AutocompleteWidget'; 11 | export const widgets = { 12 | CheckboxWidget, 13 | CheckboxesWidget, 14 | RadioWidget, 15 | RangeWidget, 16 | SelectWidget, 17 | TextareaWidget, 18 | FileWidget, 19 | AutocompleteWidget, 20 | } as RegistryWidgetsType; 21 | -------------------------------------------------------------------------------- /packages/jsonschema-page/src/Fields/Typography.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { 4 | RcTypography, 5 | } from '@ringcentral/juno'; 6 | import { TextWithMarkdown } from '../components/TextWithMarkdown'; 7 | 8 | export function Typography({ 9 | schema, 10 | uiSchema, 11 | }) { 12 | const variant = uiSchema && uiSchema['ui:variant'] || 'body1'; 13 | const isBulletedList = uiSchema && uiSchema['ui:bulletedList']; 14 | const component = isBulletedList ? 'li' : 'div'; 15 | const style = isBulletedList ? {} : { marginTop: '5px' }; 16 | return ( 17 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/lib/TabFreezePrevention.ts: -------------------------------------------------------------------------------- 1 | import * as uuid from 'uuid'; 2 | import { isFirefox } from './isFirefox'; 3 | /** 4 | * If the browser supports web lock api, obtain a web lock indefinitely. 5 | * This will prevent chrome's proactive tab freeze feature from freezing 6 | * our app. 7 | * 8 | * https://www.chromestatus.com/feature/5193677469122560 9 | * https://developer.mozilla.org/en-US/docs/Web/API/Lock 10 | * 11 | * Use randomly generated uuid to prevent lock collision. While it should not 12 | * have any affect if multiple tabs uses the same name for the lock, we want to 13 | * avoid this since the api is still experimental and might have strange results. 14 | */ 15 | 16 | if (!isFirefox()) { 17 | window.navigator?.locks?.request?.(uuid.v4(), () => { 18 | return new Promise(() => {}); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/ja-JP.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: "メイン番号", 3 | DirectNumber: "直通", 4 | MainCompanyNumber: "会社の代表番号", 5 | CompanyNumber: "会社", 6 | CompanyFaxNumber: "FAX", 7 | Blocked: "ブロック済み", 8 | AdditionalCompanyNumber: "会社", 9 | ForwardedCompanyNumber: "転送元", 10 | from: "自分の発信者番号" 11 | }; 12 | 13 | // @key: @#@"DirectNumber"@#@ @source: @#@"Direct"@#@ 14 | // @key: @#@"MainCompanyNumber"@#@ @source: @#@"Main"@#@ 15 | // @key: @#@"CompanyNumber"@#@ @source: @#@"Company"@#@ 16 | // @key: @#@"CompanyFaxNumber"@#@ @source: @#@"Fax"@#@ 17 | // @key: @#@"Blocked"@#@ @source: @#@"Blocked"@#@ 18 | // @key: @#@"AdditionalCompanyNumber"@#@ @source: @#@"Company"@#@ 19 | // @key: @#@"ForwardedCompanyNumber"@#@ @source: @#@"Forwarded"@#@ 20 | // @key: @#@"from"@#@ @source: @#@"From"@#@ 21 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/ko-KR.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: "기본 번호", 3 | DirectNumber: "직접", 4 | MainCompanyNumber: "기본 회사 번호", 5 | CompanyNumber: "회사", 6 | CompanyFaxNumber: "팩스", 7 | Blocked: "차단됨", 8 | AdditionalCompanyNumber: "회사", 9 | ForwardedCompanyNumber: "착신 전환됨", 10 | from: "내 발신자 ID" 11 | }; 12 | 13 | // @key: @#@"DirectNumber"@#@ @source: @#@"Direct"@#@ 14 | // @key: @#@"MainCompanyNumber"@#@ @source: @#@"Main"@#@ 15 | // @key: @#@"CompanyNumber"@#@ @source: @#@"Company"@#@ 16 | // @key: @#@"CompanyFaxNumber"@#@ @source: @#@"Fax"@#@ 17 | // @key: @#@"Blocked"@#@ @source: @#@"Blocked"@#@ 18 | // @key: @#@"AdditionalCompanyNumber"@#@ @source: @#@"Company"@#@ 19 | // @key: @#@"ForwardedCompanyNumber"@#@ @source: @#@"Forwarded"@#@ 20 | // @key: @#@"from"@#@ @source: @#@"From"@#@ 21 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/zh-HK.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: "主要號碼", 3 | DirectNumber: "直撥號碼", 4 | MainCompanyNumber: "主要公司號碼", 5 | CompanyNumber: "公司", 6 | CompanyFaxNumber: "傳真", 7 | Blocked: "已封鎖", 8 | AdditionalCompanyNumber: "公司", 9 | ForwardedCompanyNumber: "轉接公司號碼", 10 | from: "我的來電者 ID" 11 | }; 12 | 13 | // @key: @#@"DirectNumber"@#@ @source: @#@"Direct"@#@ 14 | // @key: @#@"MainCompanyNumber"@#@ @source: @#@"Main"@#@ 15 | // @key: @#@"CompanyNumber"@#@ @source: @#@"Company"@#@ 16 | // @key: @#@"CompanyFaxNumber"@#@ @source: @#@"Fax"@#@ 17 | // @key: @#@"Blocked"@#@ @source: @#@"Blocked"@#@ 18 | // @key: @#@"AdditionalCompanyNumber"@#@ @source: @#@"Company"@#@ 19 | // @key: @#@"ForwardedCompanyNumber"@#@ @source: @#@"Forwarded"@#@ 20 | // @key: @#@"from"@#@ @source: @#@"From"@#@ 21 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/zh-TW.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: "主要號碼", 3 | DirectNumber: "直撥號碼", 4 | MainCompanyNumber: "主要公司號碼", 5 | CompanyNumber: "公司號碼", 6 | CompanyFaxNumber: "傳真號碼", 7 | Blocked: "已封鎖", 8 | AdditionalCompanyNumber: "公司號碼", 9 | ForwardedCompanyNumber: "轉接公司號碼", 10 | from: "我的來電者 ID" 11 | }; 12 | 13 | // @key: @#@"DirectNumber"@#@ @source: @#@"Direct"@#@ 14 | // @key: @#@"MainCompanyNumber"@#@ @source: @#@"Main"@#@ 15 | // @key: @#@"CompanyNumber"@#@ @source: @#@"Company"@#@ 16 | // @key: @#@"CompanyFaxNumber"@#@ @source: @#@"Fax"@#@ 17 | // @key: @#@"Blocked"@#@ @source: @#@"Blocked"@#@ 18 | // @key: @#@"AdditionalCompanyNumber"@#@ @source: @#@"Company"@#@ 19 | // @key: @#@"ForwardedCompanyNumber"@#@ @source: @#@"Forwarded"@#@ 20 | // @key: @#@"from"@#@ @source: @#@"From"@#@ 21 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/zh-CN.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: "主号码", 3 | DirectNumber: "直拨号码", 4 | MainCompanyNumber: "主公司号码", 5 | CompanyNumber: "公司号码", 6 | CompanyFaxNumber: "传真号码", 7 | Blocked: "阻止显示主叫信息", 8 | AdditionalCompanyNumber: "公司号码", 9 | ForwardedCompanyNumber: "转接公司号码", 10 | from: "我的主叫信息显示" 11 | }; 12 | 13 | // @key: @#@"DirectNumber"@#@ @source: @#@"Direct"@#@ 14 | // @key: @#@"MainCompanyNumber"@#@ @source: @#@"Main"@#@ 15 | // @key: @#@"CompanyNumber"@#@ @source: @#@"Company"@#@ 16 | // @key: @#@"CompanyFaxNumber"@#@ @source: @#@"Fax"@#@ 17 | // @key: @#@"Blocked"@#@ @source: @#@"Blocked"@#@ 18 | // @key: @#@"AdditionalCompanyNumber"@#@ @source: @#@"Company"@#@ 19 | // @key: @#@"ForwardedCompanyNumber"@#@ @source: @#@"Forwarded"@#@ 20 | // @key: @#@"from"@#@ @source: @#@"From"@#@ 21 | -------------------------------------------------------------------------------- /src/lib/lockRefresh.js: -------------------------------------------------------------------------------- 1 | import { isFirefox } from "./isFirefox"; 2 | 3 | export default function lockRefresh(sdk) { 4 | if (!navigator.locks || isFirefox()) { 5 | return sdk; 6 | } 7 | const platform = sdk.platform(); 8 | platform._$$refresh = platform._refresh; 9 | const refreshWithLock = () => { 10 | return navigator.locks.request('token_refresh', {mode: 'exclusive'}, async lock => { 11 | const isRefreshed = await platform._auth.accessTokenValid() 12 | if (isRefreshed) { 13 | const authData = await platform._auth.data(); 14 | return new Response(JSON.stringify(authData), { 15 | status: 200, 16 | }); 17 | }; 18 | const res = await platform._$$refresh(); 19 | return res; 20 | }); 21 | }; 22 | platform._refresh = refreshWithLock; 23 | return sdk; 24 | } 25 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/fi-FI.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: "Ensisijainen numero", 3 | DirectNumber: "Suora", 4 | MainCompanyNumber: "Yrityksen päänumero", 5 | CompanyNumber: "Yritys", 6 | CompanyFaxNumber: "Faksi", 7 | Blocked: "Estetty", 8 | AdditionalCompanyNumber: "Yritys", 9 | ForwardedCompanyNumber: "Siirretty", 10 | from: "Oma soittajatunnus" 11 | }; 12 | 13 | // @key: @#@"DirectNumber"@#@ @source: @#@"Direct"@#@ 14 | // @key: @#@"MainCompanyNumber"@#@ @source: @#@"Main"@#@ 15 | // @key: @#@"CompanyNumber"@#@ @source: @#@"Company"@#@ 16 | // @key: @#@"CompanyFaxNumber"@#@ @source: @#@"Fax"@#@ 17 | // @key: @#@"Blocked"@#@ @source: @#@"Blocked"@#@ 18 | // @key: @#@"AdditionalCompanyNumber"@#@ @source: @#@"Company"@#@ 19 | // @key: @#@"ForwardedCompanyNumber"@#@ @source: @#@"Forwarded"@#@ 20 | // @key: @#@"from"@#@ @source: @#@"From"@#@ 21 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/nl-NL.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: "Primair nummer", 3 | DirectNumber: "Direct", 4 | MainCompanyNumber: "Centrale bedrijfsnummer", 5 | CompanyNumber: "Bedrijf", 6 | CompanyFaxNumber: "Fax", 7 | Blocked: "Geblokkeerd", 8 | AdditionalCompanyNumber: "Bedrijf", 9 | ForwardedCompanyNumber: "Doorverbonden", 10 | from: "Mijn beller-ID" 11 | }; 12 | 13 | // @key: @#@"DirectNumber"@#@ @source: @#@"Direct"@#@ 14 | // @key: @#@"MainCompanyNumber"@#@ @source: @#@"Main"@#@ 15 | // @key: @#@"CompanyNumber"@#@ @source: @#@"Company"@#@ 16 | // @key: @#@"CompanyFaxNumber"@#@ @source: @#@"Fax"@#@ 17 | // @key: @#@"Blocked"@#@ @source: @#@"Blocked"@#@ 18 | // @key: @#@"AdditionalCompanyNumber"@#@ @source: @#@"Company"@#@ 19 | // @key: @#@"ForwardedCompanyNumber"@#@ @source: @#@"Forwarded"@#@ 20 | // @key: @#@"from"@#@ @source: @#@"From"@#@ 21 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/es-ES.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: "Número principal", 3 | DirectNumber: "Directo", 4 | MainCompanyNumber: "Número principal de la empresa", 5 | CompanyNumber: "Empresa", 6 | CompanyFaxNumber: "Fax", 7 | Blocked: "Bloqueado", 8 | AdditionalCompanyNumber: "Empresa", 9 | ForwardedCompanyNumber: "Desviado", 10 | from: "Mi ID de llamadas" 11 | }; 12 | 13 | // @key: @#@"DirectNumber"@#@ @source: @#@"Direct"@#@ 14 | // @key: @#@"MainCompanyNumber"@#@ @source: @#@"Main"@#@ 15 | // @key: @#@"CompanyNumber"@#@ @source: @#@"Company"@#@ 16 | // @key: @#@"CompanyFaxNumber"@#@ @source: @#@"Fax"@#@ 17 | // @key: @#@"Blocked"@#@ @source: @#@"Blocked"@#@ 18 | // @key: @#@"AdditionalCompanyNumber"@#@ @source: @#@"Company"@#@ 19 | // @key: @#@"ForwardedCompanyNumber"@#@ @source: @#@"Forwarded"@#@ 20 | // @key: @#@"from"@#@ @source: @#@"From"@#@ 21 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/it-IT.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: "Numero principale", 3 | DirectNumber: "Diretto", 4 | MainCompanyNumber: "Numero aziendale principale", 5 | CompanyNumber: "Azienda", 6 | CompanyFaxNumber: "Fax", 7 | Blocked: "Bloccato", 8 | AdditionalCompanyNumber: "Azienda", 9 | ForwardedCompanyNumber: "Trasferita", 10 | from: "Il mio ID chiamante" 11 | }; 12 | 13 | // @key: @#@"DirectNumber"@#@ @source: @#@"Direct"@#@ 14 | // @key: @#@"MainCompanyNumber"@#@ @source: @#@"Main"@#@ 15 | // @key: @#@"CompanyNumber"@#@ @source: @#@"Company"@#@ 16 | // @key: @#@"CompanyFaxNumber"@#@ @source: @#@"Fax"@#@ 17 | // @key: @#@"Blocked"@#@ @source: @#@"Blocked"@#@ 18 | // @key: @#@"AdditionalCompanyNumber"@#@ @source: @#@"Company"@#@ 19 | // @key: @#@"ForwardedCompanyNumber"@#@ @source: @#@"Forwarded"@#@ 20 | // @key: @#@"from"@#@ @source: @#@"From"@#@ 21 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/pt-BR.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: "Número principal", 3 | DirectNumber: "Direto", 4 | MainCompanyNumber: "Número principal da empresa", 5 | CompanyNumber: "Empresa", 6 | CompanyFaxNumber: "Fax", 7 | Blocked: "Bloqueado", 8 | AdditionalCompanyNumber: "Empresa", 9 | ForwardedCompanyNumber: "Encaminhado", 10 | from: "Minha ID da chamada" 11 | }; 12 | 13 | // @key: @#@"DirectNumber"@#@ @source: @#@"Direct"@#@ 14 | // @key: @#@"MainCompanyNumber"@#@ @source: @#@"Main"@#@ 15 | // @key: @#@"CompanyNumber"@#@ @source: @#@"Company"@#@ 16 | // @key: @#@"CompanyFaxNumber"@#@ @source: @#@"Fax"@#@ 17 | // @key: @#@"Blocked"@#@ @source: @#@"Blocked"@#@ 18 | // @key: @#@"AdditionalCompanyNumber"@#@ @source: @#@"Company"@#@ 19 | // @key: @#@"ForwardedCompanyNumber"@#@ @source: @#@"Forwarded"@#@ 20 | // @key: @#@"from"@#@ @source: @#@"From"@#@ 21 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/fr-FR.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: "Numéro principal", 3 | DirectNumber: "Direct", 4 | MainCompanyNumber: "Numéro principal de l’entreprise", 5 | CompanyNumber: "Entreprise", 6 | CompanyFaxNumber: "Fax", 7 | Blocked: "Bloqué", 8 | AdditionalCompanyNumber: "Entreprise", 9 | ForwardedCompanyNumber: "Renvoyé", 10 | from: "Mon ID d’appelant" 11 | }; 12 | 13 | // @key: @#@"DirectNumber"@#@ @source: @#@"Direct"@#@ 14 | // @key: @#@"MainCompanyNumber"@#@ @source: @#@"Main"@#@ 15 | // @key: @#@"CompanyNumber"@#@ @source: @#@"Company"@#@ 16 | // @key: @#@"CompanyFaxNumber"@#@ @source: @#@"Fax"@#@ 17 | // @key: @#@"Blocked"@#@ @source: @#@"Blocked"@#@ 18 | // @key: @#@"AdditionalCompanyNumber"@#@ @source: @#@"Company"@#@ 19 | // @key: @#@"ForwardedCompanyNumber"@#@ @source: @#@"Forwarded"@#@ 20 | // @key: @#@"from"@#@ @source: @#@"From"@#@ 21 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/pt-PT.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: "Número principal", 3 | DirectNumber: "Direto", 4 | MainCompanyNumber: "Número principal da empresa", 5 | CompanyNumber: "Empresa", 6 | CompanyFaxNumber: "Fax", 7 | Blocked: "Bloqueado", 8 | AdditionalCompanyNumber: "Empresa", 9 | ForwardedCompanyNumber: "Reencaminhado", 10 | from: "O meu ID de chamada" 11 | }; 12 | 13 | // @key: @#@"DirectNumber"@#@ @source: @#@"Direct"@#@ 14 | // @key: @#@"MainCompanyNumber"@#@ @source: @#@"Main"@#@ 15 | // @key: @#@"CompanyNumber"@#@ @source: @#@"Company"@#@ 16 | // @key: @#@"CompanyFaxNumber"@#@ @source: @#@"Fax"@#@ 17 | // @key: @#@"Blocked"@#@ @source: @#@"Blocked"@#@ 18 | // @key: @#@"AdditionalCompanyNumber"@#@ @source: @#@"Company"@#@ 19 | // @key: @#@"ForwardedCompanyNumber"@#@ @source: @#@"Forwarded"@#@ 20 | // @key: @#@"from"@#@ @source: @#@"From"@#@ 21 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/en-AU.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: 'Primary number', 3 | DirectNumber: 'Direct number', 4 | MainCompanyNumber: 'Main company number', 5 | CompanyNumber: 'Company number', 6 | CompanyFaxNumber: 'Fax number', 7 | Blocked: 'Blocked', 8 | AdditionalCompanyNumber: 'Company number', 9 | ForwardedCompanyNumber: 'Forwarded number', 10 | from: 'My caller ID', 11 | }; 12 | 13 | // @key: @#@"DirectNumber"@#@ @source: @#@"Direct"@#@ 14 | // @key: @#@"MainCompanyNumber"@#@ @source: @#@"Main"@#@ 15 | // @key: @#@"CompanyNumber"@#@ @source: @#@"Company"@#@ 16 | // @key: @#@"CompanyFaxNumber"@#@ @source: @#@"Fax"@#@ 17 | // @key: @#@"Blocked"@#@ @source: @#@"Blocked"@#@ 18 | // @key: @#@"AdditionalCompanyNumber"@#@ @source: @#@"Company"@#@ 19 | // @key: @#@"ForwardedCompanyNumber"@#@ @source: @#@"Forwarded"@#@ 20 | // @key: @#@"from"@#@ @source: @#@"From"@#@ 21 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/en-GB.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: 'Primary number', 3 | DirectNumber: 'Direct number', 4 | MainCompanyNumber: 'Main company number', 5 | CompanyNumber: 'Company number', 6 | CompanyFaxNumber: 'Fax number', 7 | Blocked: 'Blocked', 8 | AdditionalCompanyNumber: 'Company number', 9 | ForwardedCompanyNumber: 'Forwarded number', 10 | from: 'My caller ID', 11 | }; 12 | 13 | // @key: @#@"DirectNumber"@#@ @source: @#@"Direct"@#@ 14 | // @key: @#@"MainCompanyNumber"@#@ @source: @#@"Main"@#@ 15 | // @key: @#@"CompanyNumber"@#@ @source: @#@"Company"@#@ 16 | // @key: @#@"CompanyFaxNumber"@#@ @source: @#@"Fax"@#@ 17 | // @key: @#@"Blocked"@#@ @source: @#@"Blocked"@#@ 18 | // @key: @#@"AdditionalCompanyNumber"@#@ @source: @#@"Company"@#@ 19 | // @key: @#@"ForwardedCompanyNumber"@#@ @source: @#@"Forwarded"@#@ 20 | // @key: @#@"from"@#@ @source: @#@"From"@#@ 21 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/es-419.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: "Número principal", 3 | DirectNumber: "Directo", 4 | MainCompanyNumber: "Número principal de la empresa", 5 | CompanyNumber: "Empresa", 6 | CompanyFaxNumber: "Fax", 7 | Blocked: "Bloqueado", 8 | AdditionalCompanyNumber: "Empresa", 9 | ForwardedCompanyNumber: "Reenviado", 10 | from: "Mi identificador de llamadas" 11 | }; 12 | 13 | // @key: @#@"DirectNumber"@#@ @source: @#@"Direct"@#@ 14 | // @key: @#@"MainCompanyNumber"@#@ @source: @#@"Main"@#@ 15 | // @key: @#@"CompanyNumber"@#@ @source: @#@"Company"@#@ 16 | // @key: @#@"CompanyFaxNumber"@#@ @source: @#@"Fax"@#@ 17 | // @key: @#@"Blocked"@#@ @source: @#@"Blocked"@#@ 18 | // @key: @#@"AdditionalCompanyNumber"@#@ @source: @#@"Company"@#@ 19 | // @key: @#@"ForwardedCompanyNumber"@#@ @source: @#@"Forwarded"@#@ 20 | // @key: @#@"from"@#@ @source: @#@"From"@#@ 21 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/fr-CA.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: "Numéro principal", 3 | DirectNumber: "Direct", 4 | MainCompanyNumber: "Numéro principal de l’entreprise", 5 | CompanyNumber: "Entreprise", 6 | CompanyFaxNumber: "Fax", 7 | Blocked: "Bloqué", 8 | AdditionalCompanyNumber: "Entreprise", 9 | ForwardedCompanyNumber: "Transféré", 10 | from: "Mon identification d’appelant" 11 | }; 12 | 13 | // @key: @#@"DirectNumber"@#@ @source: @#@"Direct"@#@ 14 | // @key: @#@"MainCompanyNumber"@#@ @source: @#@"Main"@#@ 15 | // @key: @#@"CompanyNumber"@#@ @source: @#@"Company"@#@ 16 | // @key: @#@"CompanyFaxNumber"@#@ @source: @#@"Fax"@#@ 17 | // @key: @#@"Blocked"@#@ @source: @#@"Blocked"@#@ 18 | // @key: @#@"AdditionalCompanyNumber"@#@ @source: @#@"Company"@#@ 19 | // @key: @#@"ForwardedCompanyNumber"@#@ @source: @#@"Forwarded"@#@ 20 | // @key: @#@"from"@#@ @source: @#@"From"@#@ 21 | -------------------------------------------------------------------------------- /src/components/NavigationBar/interface.ts: -------------------------------------------------------------------------------- 1 | import type { ReactElement } from 'react'; 2 | import type { NavigationButtonProps } from './NavigationButton'; 3 | 4 | interface ActionInHeader { 5 | icon: ReactElement, 6 | onClick: () => void, 7 | title: string, 8 | } 9 | 10 | export interface TabPropTypes extends Partial { 11 | path: string; 12 | virtualPath?: string; 13 | isActive: (path: string, virtualPath?: string) => boolean; 14 | noticeCounts?: number; 15 | childTabs?: TabPropTypes[]; 16 | showHeader: (path: string) => boolean; 17 | showHeaderBorder?: boolean; 18 | actionsInHeaderRight?: ActionInHeader[]; 19 | hideSideDrawerExtendedButton?: boolean; 20 | } 21 | 22 | export interface NavigationBarProps { 23 | tabs: TabPropTypes[]; 24 | goTo?: (path: string, virtualPath?: string) => any; 25 | currentPath: string; 26 | currentVirtualPath?: string; 27 | } 28 | -------------------------------------------------------------------------------- /src/components/DialerPanel/FromField/i18n/de-DE.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: "Primäre Nummer", 3 | DirectNumber: "Durchwahlnummer", 4 | MainCompanyNumber: "Hauptunternehmensnummer", 5 | CompanyNumber: "Unternehmen", 6 | CompanyFaxNumber: "Fax", 7 | Blocked: "Rufnummernunterdrückung", 8 | AdditionalCompanyNumber: "Unternehmen", 9 | ForwardedCompanyNumber: "Weitergeleitet", 10 | from: "Eigene Anrufer-ID" 11 | }; 12 | 13 | // @key: @#@"DirectNumber"@#@ @source: @#@"Direct"@#@ 14 | // @key: @#@"MainCompanyNumber"@#@ @source: @#@"Main"@#@ 15 | // @key: @#@"CompanyNumber"@#@ @source: @#@"Company"@#@ 16 | // @key: @#@"CompanyFaxNumber"@#@ @source: @#@"Fax"@#@ 17 | // @key: @#@"Blocked"@#@ @source: @#@"Blocked"@#@ 18 | // @key: @#@"AdditionalCompanyNumber"@#@ @source: @#@"Company"@#@ 19 | // @key: @#@"ForwardedCompanyNumber"@#@ @source: @#@"Forwarded"@#@ 20 | // @key: @#@"from"@#@ @source: @#@"From"@#@ 21 | -------------------------------------------------------------------------------- /src/lib/isDuplicated.ts: -------------------------------------------------------------------------------- 1 | const getStorageKey = (groupName: string, id) => `${groupName}-${id}`; 2 | 3 | function clearExpiredKeys(groupName: string, maxKeys = 20) { 4 | const keys = Object.keys(localStorage); 5 | const groupKeys = keys.filter((key) => key.startsWith(groupName)); 6 | if (groupKeys.length > maxKeys) { 7 | const sortedKeys = groupKeys.sort((a, b) => { 8 | return Number(localStorage.getItem(a)) - Number(localStorage.getItem(b)); 9 | }); 10 | localStorage.removeItem(sortedKeys[0]); 11 | } 12 | } 13 | 14 | export function isDuplicated(groupName: string, id, maxKeys = 20) { 15 | const key = getStorageKey(groupName, id); 16 | if (localStorage.getItem(key)) { 17 | return true; 18 | } 19 | try { 20 | localStorage.setItem(key, `${Date.now()}`); 21 | clearExpiredKeys(groupName, maxKeys); 22 | } catch (e) { 23 | console.error(e); 24 | } 25 | return false; 26 | } 27 | -------------------------------------------------------------------------------- /src/assets/images/popup.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Popout 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/TransferPanel/CallButton.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { 4 | styled, 5 | RcIconButton, 6 | RcTypography, 7 | } from '@ringcentral/juno'; 8 | 9 | const Container = styled.div` 10 | display: flex; 11 | flex-direction: column; 12 | align-items: center; 13 | width: 74px; 14 | `; 15 | 16 | const Title = styled(RcTypography)` 17 | margin-top: 8px; 18 | `; 19 | 20 | export function CallButton({ 21 | title, 22 | symbol, 23 | onClick, 24 | disabled, 25 | dataSign 26 | }) { 27 | return ( 28 | 29 | 37 | 38 | {title} 39 | 40 | 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Redirect 14 | 20 | 21 | 22 |
23 |

24 | Loading... 25 |

26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/components/GlipChatPanel/getGlipGroupName.ts: -------------------------------------------------------------------------------- 1 | type Group = { 2 | id?: string; 3 | name?: string; 4 | type?: string; 5 | members?: any[]; 6 | detailMembers?: any[]; 7 | latestPost?: any; 8 | unread?: number; 9 | }; 10 | 11 | export function getGlipGroupName({ group, showNumber }: { 12 | group: Group; 13 | showNumber?: boolean; 14 | }) { 15 | let name = group.name; 16 | if (!name && group.detailMembers) { 17 | let noMes = group.detailMembers.filter((m) => !m.isMe); 18 | if (noMes.length === 0) { 19 | noMes = group.detailMembers; 20 | } 21 | const names = noMes.map( 22 | (p) => 23 | `${p.firstName ? p.firstName : ''} ${p.lastName ? p.lastName : ''}`, 24 | ); 25 | name = names.join(', '); 26 | } 27 | let number = ''; 28 | if (showNumber && group.members && group.members.length > 2) { 29 | number = ` (${group.members.length})`; 30 | } 31 | return `${name}${number}`; 32 | } 33 | -------------------------------------------------------------------------------- /src/components/CallItem/StatusMessage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { RcText, styled } from '@ringcentral/juno'; 4 | 5 | export function StatusMessage({ statusMatch, className }: { 6 | statusMatch?: { message: string; status: string }; 7 | className?: string; 8 | }) { 9 | if (!statusMatch) { 10 | return null; 11 | } 12 | const { message, status } = statusMatch; 13 | if (!message) { 14 | return null; 15 | } 16 | let color: string | undefined = undefined; 17 | if (status === 'pending') { 18 | color = 'warning.f02'; 19 | } else if (status === 'failed') { 20 | color = 'danger.f02'; 21 | } else if (status === 'success') { 22 | color = 'success.f02'; 23 | } 24 | return ( 25 | 32 | {message} 33 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/components/MeetingScheduleButton/MeetingScheduleButtonWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { css, palette2, RcButton, styled } from '@ringcentral/juno'; 2 | 3 | export const ScheduleButton = styled(RcButton)``; 4 | 5 | export const MeetingScheduleButtonWrapper = styled.div<{ $hidden: boolean }>` 6 | flex-shrink: 0; 7 | padding: 3px 20px 16px; 8 | background-color: transparent; 9 | box-shadow: none; 10 | 11 | ${({ $hidden }) => 12 | $hidden 13 | ? css` 14 | display: flex; 15 | flex-direction: column; 16 | justify-content: center; 17 | align-items: center; 18 | height: 100%; 19 | margin-top: -20px; 20 | ` 21 | : css` 22 | border-top: 1px solid ${palette2('neutral', 'l02')}; 23 | position: relative; 24 | background-color: ${palette2('neutral', 'b01')}; 25 | `}; 26 | 27 | ${ScheduleButton} { 28 | margin-top: 13px; 29 | } 30 | `; 31 | -------------------------------------------------------------------------------- /src/modules/Adapter/getReducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import getModuleStatusReducer 4 | from '@ringcentral-integration/commons/lib/getModuleStatusReducer'; 5 | 6 | function getFocusReducer(types) { 7 | return (state = false, { type, focus }) => { 8 | switch (type) { 9 | case types.setFocus: 10 | return !!focus; 11 | default: 12 | return state; 13 | } 14 | }; 15 | } 16 | 17 | function getShowDemoWarningReducer(types) { 18 | return (state = false, { type, show }) => { 19 | switch (type) { 20 | case types.setShowDemoWarning: 21 | return !!show; 22 | default: 23 | return state; 24 | } 25 | }; 26 | } 27 | 28 | export default function getReducer(types) { 29 | return combineReducers({ 30 | status: getModuleStatusReducer(types), 31 | showDemoWarning: getShowDemoWarningReducer(types), 32 | focus: getFocusReducer(types), 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /docs/config/setting-params.md: -------------------------------------------------------------------------------- 1 | # Setting RingCentral Embeddable configuration parameters 2 | 3 | RingCentral Embeddable supports a number of different configuration parameters to modify the behavior of the library is key ways. Each parameter is set in one of two ways. 4 | 5 | ### Via script tag's `src` attribute 6 | 7 | ```js 8 | 16 | ``` 17 | 18 | ### Via iframe's `href` attribute 19 | 20 | ```html 21 | 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /src/components/ActiveCallItem/CallIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { RcAvatar, RcIcon, styled } from '@ringcentral/juno'; 4 | import { 5 | People, 6 | Conference, 7 | } from '@ringcentral/juno-icon'; 8 | 9 | const StyledAvatar = styled(RcAvatar)` 10 | padding-left: 16px; 11 | 12 | .RcAvatar-avatarContainer { 13 | width: 26px; 14 | height: 26px; 15 | } 16 | 17 | .icon { 18 | font-size: 15px; 19 | } 20 | `; 21 | 22 | export function CallIcon({ 23 | isOnConferenceCall = false, 24 | avatarUrl = '', 25 | }) { 26 | let icon; 27 | if (!avatarUrl || isOnConferenceCall) { 28 | icon = ( 29 | 33 | ); 34 | } 35 | return ( 36 | 41 | {icon} 42 | 43 | ); 44 | } -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | setBrowserPermission, 3 | visitIndexPage, 4 | } = require('./steps/common'); 5 | const { waitForTimeout } = require('./helpers'); 6 | 7 | const { IframeWidget } = require('./steps/IframeWidget'); 8 | 9 | describe('Index page test', () => { 10 | beforeAll(async () => { 11 | await setBrowserPermission(); 12 | await visitIndexPage(); 13 | }); 14 | 15 | it('should display "RingCentral Embeddable" text on page', async () => { 16 | const title = await page.$eval('h1', (el) => el.innerText); 17 | expect(title).toContain('RingCentral Embeddable'); 18 | }); 19 | 20 | it('should get SignIn in widget iframe', async () => { 21 | const widgetIframe = new IframeWidget(); 22 | await widgetIframe.loadElement(); 23 | await widgetIframe.waitForLoginPage(); 24 | await waitForTimeout(1000); 25 | const loginText = await widgetIframe.getLoginButtonText(); 26 | expect(loginText).toEqual('Sign In'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/modules/GlipCompany/index.js: -------------------------------------------------------------------------------- 1 | import { GlipCompany as GlipCompanyBase } from '@ringcentral-integration/commons/modules/GlipCompany'; 2 | import { Module } from '@ringcentral-integration/commons/lib/di'; 3 | 4 | @Module({ 5 | deps: [ 6 | 'AppFeatures', 7 | ], 8 | }) 9 | export class GlipCompany extends GlipCompanyBase { 10 | constructor(deps) { 11 | super(deps); 12 | // TODO: remove this when handled in widget lib 13 | this._source._props.fetchFunction = async () => { 14 | try { 15 | const response = await deps.client.glip().companies('~').get(); 16 | return response; 17 | } catch (error) { 18 | if ( 19 | error && 20 | error.message === 'Company associated with RC account is not found' 21 | ) { 22 | this._deps.appFeatures.setConfigState({ 23 | Glip: false, 24 | }); 25 | return {}; 26 | } 27 | throw error; 28 | } 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/jsonschema-page/.npmignore: -------------------------------------------------------------------------------- 1 | # Source files (npm package is in npm-package/ directory) 2 | src/ 3 | tsconfig*.json 4 | build.js 5 | test-esm-imports.js 6 | 7 | # Build directories 8 | dist/ 9 | npm-package/ 10 | 11 | # Development files 12 | .storybook/ 13 | storybook-static/ 14 | **/*.stories.* 15 | **/__stories__/ 16 | 17 | # Build artifacts (temporary) 18 | tsconfig.build.*.json 19 | 20 | # IDE and OS files 21 | .DS_Store 22 | .vscode/ 23 | .idea/ 24 | *.swp 25 | *.swo 26 | 27 | # Test files 28 | **/*.test.* 29 | **/*.spec.* 30 | test/ 31 | tests/ 32 | __tests__/ 33 | 34 | # Documentation (keep README.md) 35 | docs/ 36 | *.md 37 | !README.md 38 | RELEASE_GUIDE.md 39 | DEVELOPMENT.md 40 | 41 | # Logs and temporary files 42 | *.log 43 | npm-debug.log* 44 | yarn-debug.log* 45 | yarn-error.log* 46 | .npm 47 | 48 | # Coverage 49 | coverage/ 50 | .nyc_output/ 51 | 52 | # Environment files 53 | .env* 54 | 55 | # Node modules (should be excluded by default but being explicit) 56 | node_modules/ -------------------------------------------------------------------------------- /packages/jsonschema-page/src/Templates/FieldErrorTemplate.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | RcListItem as ListItem, 4 | RcFormHelperText as FormHelperText, 5 | RcList as List, 6 | } from '@ringcentral/juno'; 7 | 8 | import { errorId, FieldErrorProps, FormContextType, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils'; 9 | 10 | export default function FieldErrorTemplate< 11 | T = any, 12 | S extends StrictRJSFSchema = RJSFSchema, 13 | F extends FormContextType = any 14 | >(props: FieldErrorProps) { 15 | const { errors = [], idSchema } = props; 16 | if (errors.length === 0) { 17 | return null; 18 | } 19 | const id = errorId(idSchema); 20 | 21 | return ( 22 | 23 | {errors.map((error, i: number) => { 24 | return ( 25 | 26 | {error} 27 | 28 | ); 29 | })} 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/lib/patchGetUserMedia.js: -------------------------------------------------------------------------------- 1 | if (navigator.mediaDevices.getUserMedia) { 2 | navigator.mediaDevices._$getUserMedia = navigator.mediaDevices.getUserMedia; 3 | navigator.mediaDevices.getUserMedia = async (constraints) => { 4 | if (!document.hidden) { 5 | return navigator.mediaDevices._$getUserMedia(constraints); 6 | } 7 | // show alert if not response after 3s 8 | let timeout = setTimeout(() => { 9 | timeout = null; 10 | if (window.phone && window.phone.alert) { 11 | window.phone.alert.warning({ 12 | message: 'allowMicrophonePermissionOnInactiveTab', 13 | ttl: 0, 14 | }); 15 | } 16 | }, 3000); 17 | try { 18 | const result = await navigator.mediaDevices._$getUserMedia(constraints); 19 | if (timeout !== null) { 20 | clearTimeout(timeout); 21 | } 22 | return result; 23 | } catch (e) { 24 | if (timeout !== null) { 25 | clearTimeout(timeout); 26 | } 27 | throw e; 28 | } 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/components/ConfirmDialog/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { 4 | RcButton, 5 | RcTypography, 6 | RcDialog, 7 | RcDialogContent, 8 | RcDialogActions, 9 | } from '@ringcentral/juno'; 10 | 11 | export function ConfirmDialog({ 12 | open, 13 | onClose, 14 | onConfirm, 15 | title, 16 | confirmText = 'Confirm', 17 | confirmButtonColor = 'primary', 18 | keepMounted = false, 19 | onClick = undefined, 20 | }) { 21 | return ( 22 | 28 | 29 | {title} 30 | 31 | 32 | 33 | Cancel 34 | 35 | 36 | {confirmText} 37 | 38 | 39 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /src/modules/GlipGroups/index.js: -------------------------------------------------------------------------------- 1 | import GlipGroups from '@ringcentral-integration/commons/modules/GlipGroups'; 2 | import { Module } from '@ringcentral-integration/commons/lib/di'; 3 | 4 | @Module({ 5 | deps: [ 6 | 'AppFeatures', 7 | ], 8 | }) 9 | export default class NewGlipGroups extends GlipGroups { 10 | constructor({ appFeatures, ...options }) { 11 | super(options); 12 | this._appFeatures = appFeatures; 13 | } 14 | 15 | // TODO: update permission check in widgets lib 16 | get _hasPermission() { 17 | return !!this._appFeatures.hasGlipPermission; 18 | } 19 | 20 | // TODO: hack for 400 error (Company associated with RC account is not found) 21 | async _fetchFunction() { 22 | try { 23 | const data = await super._fetchFunction(); 24 | return data; 25 | } catch (error) { 26 | if ( 27 | error && 28 | error.message === 'Company associated with RC account is not found' 29 | ) { 30 | return []; 31 | } 32 | throw error; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/lib/requestWithPostMessage.js: -------------------------------------------------------------------------------- 1 | import uuid from 'uuid'; 2 | 3 | export default function requestWithPostMessage(path, body, timeout = 3000, target = window.parent, prefix = 'rc-post-message') { 4 | return new Promise((resolve, reject) => { 5 | const id = uuid.v4(); 6 | let responseFunc; 7 | const catchTimeout = setTimeout(() => { 8 | window.removeEventListener('message', responseFunc); 9 | reject(Error('Time out')); 10 | }, timeout); 11 | responseFunc = (e) => { 12 | const data = e.data; 13 | if ( 14 | data && 15 | data.type === `${prefix}-response` && 16 | data.responseId === id 17 | ) { 18 | clearTimeout(catchTimeout); 19 | window.removeEventListener('message', responseFunc); 20 | resolve(data.response); 21 | } 22 | }; 23 | target.postMessage({ 24 | type: `${prefix}-request`, 25 | requestId: id, 26 | path, 27 | body, 28 | }, '*'); 29 | window.addEventListener('message', responseFunc); 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /src/lib/searchContactPhoneNumbers.js: -------------------------------------------------------------------------------- 1 | export default function searchContactPhoneNumbers(contacts, searchString, entityType = 'contact') { 2 | const searchText = searchString.toLowerCase(); 3 | const result = []; 4 | contacts.forEach((item) => { 5 | const name = item.name || `${item.firstName} ${item.lastName}`; 6 | const nameSearched = `${item.firstName} ${item.lastName} ${item.name}`.toLowerCase(); 7 | if (item.phoneNumbers) { 8 | item.phoneNumbers.forEach((p) => { 9 | if ( 10 | nameSearched.indexOf(searchText) >= 0 || 11 | (p.phoneNumber && p.phoneNumber.indexOf(searchText) >= 0) 12 | ) { 13 | result.push({ 14 | id: `${item.id}${p.phoneNumber}`, 15 | name, 16 | type: item.type, 17 | phoneNumber: p.phoneNumber, 18 | phoneType: p.phoneType.replace('Phone', ''), 19 | entityType, 20 | contactId: item.id, 21 | }); 22 | } 23 | }); 24 | } 25 | }); 26 | return result; 27 | } 28 | -------------------------------------------------------------------------------- /src/components/WidgetAppsPanel/styled.ts: -------------------------------------------------------------------------------- 1 | import { styled, palette2 } from '@ringcentral/juno'; 2 | 3 | export const Container = styled.div` 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | box-sizing: border-box; 8 | flex: 1; 9 | height: 100%; 10 | width: 100%; 11 | background: ${palette2('neutral', 'b01')}; 12 | color: ${palette2('neutral', 'f06')}; 13 | `; 14 | 15 | export const PageHeader = styled.div` 16 | padding: 0 16px; 17 | padding-right: 8px; 18 | display: flex; 19 | flex-direction: row; 20 | align-items: center; 21 | border-bottom: 1px solid ${palette2('neutral', 'l02')}; 22 | width: 100%; 23 | height: 48px; 24 | box-sizing: border-box; 25 | align-items: center; 26 | `; 27 | 28 | export const Content = styled.div` 29 | flex: 1; 30 | width: 100%; 31 | padding: 16px; 32 | box-sizing: border-box; 33 | overflow-y: auto; 34 | `; 35 | 36 | export const AppIcon = styled.img` 37 | width: 32px; 38 | height: 32px; 39 | border-radius: 4px; 40 | overflow: hidden; 41 | `; 42 | -------------------------------------------------------------------------------- /packages/jsonschema-page/src/Templates/SubmitButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | RcBox as Box, 4 | RcButton as Button, 5 | } from '@ringcentral/juno'; 6 | 7 | import { getSubmitButtonOptions, FormContextType, RJSFSchema, StrictRJSFSchema, SubmitButtonProps } from '@rjsf/utils'; 8 | 9 | /** The `SubmitButton` renders a button that represent the `Submit` action on a form 10 | */ 11 | export default function SubmitButton< 12 | T = any, 13 | S extends StrictRJSFSchema = RJSFSchema, 14 | F extends FormContextType = any 15 | >({ uiSchema }: SubmitButtonProps) { 16 | const { submitText, norender, props: submitButtonProps = {} } = getSubmitButtonOptions(uiSchema); 17 | if (norender) { 18 | return null; 19 | } 20 | return ( 21 | 22 | 31 | 32 | ); 33 | 34 | } -------------------------------------------------------------------------------- /src/components/AdditionalToolbarButton/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { RcTooltip } from '@ringcentral/juno'; 4 | import { styled } from '@ringcentral/juno/foundation'; 5 | 6 | const StyledImage = styled.img` 7 | width: 20px; 8 | height: 20px; 9 | `; 10 | 11 | const Container = styled.div` 12 | width: 40px; 13 | height: 40px; 14 | display: flex; 15 | flex-direction: row; 16 | align-items: center; 17 | justify-content: center; 18 | border-radius: 50%; 19 | cursor: pointer; 20 | transition: background 150ms cubic-bezier(0.4,0,0.2,1) 0ms; 21 | 22 | :hover { 23 | background-color: rgba(101,108,128,0.08); 24 | color: rgba(101,108,128,0.88); 25 | } 26 | `; 27 | 28 | export function AdditionalToolbarButton({ 29 | label, 30 | icon, 31 | onClick, 32 | }) { 33 | return ( 34 | 35 | 36 | 41 | 42 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /src/lib/widgetContact.ts: -------------------------------------------------------------------------------- 1 | export type WidgetContact = { 2 | id?: string; 3 | phoneNumber?: string; 4 | phoneNumbers?: { 5 | phoneNumber: string; 6 | }[]; 7 | } 8 | 9 | export function isSameContact(contact1?: WidgetContact | null, contact2?: WidgetContact | null) { 10 | if (!contact1 || !contact2) { 11 | return false; 12 | } 13 | if ( 14 | contact1.id && 15 | contact2.id && 16 | contact1.id === contact2.id 17 | ) { 18 | return true; 19 | } 20 | if ( 21 | contact1.phoneNumber && 22 | contact2.phoneNumber && 23 | contact1.phoneNumber === contact2.phoneNumber 24 | ) { 25 | return true 26 | } 27 | if ( 28 | contact1.phoneNumbers && 29 | contact2.phoneNumber && 30 | contact1.phoneNumbers.some((p) => p.phoneNumber === contact2.phoneNumber) 31 | ) { 32 | return true; 33 | } 34 | if ( 35 | contact2.phoneNumbers && 36 | contact1.phoneNumber && 37 | contact2.phoneNumbers.some((p) => p.phoneNumber === contact1.phoneNumber) 38 | ) { 39 | return true; 40 | } 41 | return false; 42 | } 43 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (api) => { 2 | const isTest = api.env('test'); 3 | if (isTest) { 4 | return { 5 | presets: [ 6 | [ 7 | '@babel/preset-env', 8 | { 9 | targets: { 10 | node: 'current', 11 | }, 12 | }, 13 | ], 14 | ], 15 | }; 16 | } 17 | return { 18 | presets: [ 19 | ['@babel/preset-env', { useBuiltIns: 'usage', modules: 'auto', corejs: 3 }], 20 | '@babel/preset-react', 21 | ['@babel/preset-typescript', { 22 | isTSX: true, 23 | allExtensions: true 24 | }] 25 | ], 26 | plugins: [ 27 | '@babel/plugin-proposal-export-default-from', 28 | '@babel/plugin-proposal-function-bind', 29 | '@babel/plugin-transform-optional-chaining', 30 | '@babel/plugin-transform-nullish-coalescing-operator', 31 | ['@babel/plugin-proposal-decorators', { legacy: true }], 32 | ['@babel/plugin-transform-class-properties', { loose: true }], 33 | 'const-enum', 34 | ], 35 | sourceMaps: true 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /test/steps/common.js: -------------------------------------------------------------------------------- 1 | const { IframeWidget } = require('./IframeWidget'); 2 | const { waitForTimeout } = require('../helpers'); 3 | 4 | export async function setBrowserPermission(permissions = ['notifications', 'microphone']) { 5 | const context = browser.defaultBrowserContext(); 6 | await context.overridePermissions(__HOST_URI__, permissions); 7 | } 8 | 9 | export async function visitIndexPage() { 10 | await page.goto(__HOST_URI__, { 11 | waituntil: 'networkidle0', 12 | timeout: 150000, 13 | }); 14 | } 15 | 16 | export async function getAuthorizedWidget(jwtToken) { 17 | const widgetIframe = new IframeWidget(); 18 | await widgetIframe.loadElement(); 19 | await widgetIframe.waitForLoginPage(); 20 | // const envButton = await page.$('#setEnvironment'); 21 | // await envButton.evaluate(b => b.click()); 22 | // await widgetIframe.enableSandboxEnvironment(); 23 | // await widgetIframe.waitForLoginPage(); 24 | await widgetIframe.loginWithCallbackUri(`${__HOST_URI__}/redirect.html?jwt=${jwtToken}`); 25 | await waitForTimeout(1000); 26 | return widgetIframe; 27 | } 28 | -------------------------------------------------------------------------------- /src/components/ComposeTextPanel/NoTextPermission.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { styled, RcTypography } from '@ringcentral/juno'; 3 | import NoText from './noText.svg'; 4 | 5 | const Container = styled.div` 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | height: 100%; 10 | padding: 20px 32px; 11 | `; 12 | 13 | const ImageWrapper = styled.div` 14 | width: 120px; 15 | margin-bottom:15px; 16 | `; 17 | 18 | const Description = styled(RcTypography)` 19 | width: 100%; 20 | text-align: center; 21 | margin-bottom: 15px; 22 | `; 23 | 24 | export function NoTextPermission() { 25 | return ( 26 | 27 | 28 | 29 | 30 | 31 | Your phone number is not configured to send SMS. 32 | 33 | 34 | Contact your company admin for more information. 35 | 36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /src/modules/SmsTemplates/SmsTemplates.interface.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataFetcherV2ConsumerBaseDeps, 3 | DataSourceBaseProps, 4 | } from '@ringcentral-integration/commons/modules/DataFetcherV2'; 5 | 6 | import { ExtensionFeatures } from '@ringcentral-integration/commons/modules/ExtensionFeatures'; 7 | import { DataFetcherV2 } from '@ringcentral-integration/commons/modules/DataFetcherV2'; 8 | 9 | import { AppFeatures } from '../AppFeatures'; 10 | 11 | export interface SmsTemplateOptions extends DataSourceBaseProps {} 12 | 13 | export interface Deps extends DataFetcherV2ConsumerBaseDeps { 14 | client: any; 15 | smsTemplateOptions?: SmsTemplateOptions; 16 | extensionFeatures: ExtensionFeatures; 17 | appFeatures: AppFeatures; 18 | dateFetcherV2: DataFetcherV2; 19 | } 20 | 21 | export interface SmsTemplateRecord { 22 | id?: string; 23 | displayName: string; 24 | body: { 25 | text: string; 26 | }, 27 | scope: 'Company' | 'Personal'; 28 | site: { 29 | id: string; 30 | name: string; 31 | }; 32 | } 33 | 34 | export interface SmsTemplateList { 35 | records: SmsTemplateRecord[]; 36 | } 37 | -------------------------------------------------------------------------------- /test/embeddable.test.js: -------------------------------------------------------------------------------- 1 | const { setBrowserPermission } = require('./steps/common'); 2 | const { 3 | visitThirdPartyPage, 4 | } = require('./steps/embeddable'); 5 | const { waitForTimeout } = require('./helpers'); 6 | const { IframeWidget } = require('./steps/IframeWidget'); 7 | 8 | describe('Embeddable', () => { 9 | beforeAll(async () => { 10 | await setBrowserPermission(); 11 | }); 12 | 13 | it('should embed the widget successfully', async () => { 14 | await visitThirdPartyPage(); 15 | await page.evaluate(() => { 16 | (function () { 17 | var rcs = document.createElement('script'); 18 | rcs.src = 'http://localhost:8080/adapter.js'; 19 | var rcs0 = document.getElementsByTagName('script')[0]; 20 | rcs0.parentNode.insertBefore(rcs, rcs0); 21 | })(); 22 | }); 23 | const widgetIframe = new IframeWidget(); 24 | await widgetIframe.loadElement(); 25 | await widgetIframe.waitForLoginPage(); 26 | await waitForTimeout(1000); 27 | const loginText = await widgetIframe.getLoginButtonText(); 28 | expect(loginText).toEqual('Sign In'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/containers/ThirdPartyMeetingScheduleButton/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import withPhone from '@ringcentral-integration/widgets/lib/withPhone'; 4 | 5 | import MeetingScheduleButton from '../../components/MeetingScheduleButton'; 6 | import { RcVideoScheduleButton } from '../../components/RcVideoScheduleButton'; 7 | 8 | function MeetingInviteButton(props) { 9 | const { 10 | isRCV, 11 | inviteTitle, 12 | } = props; 13 | if (isRCV) { 14 | return ( 15 | 19 | ); 20 | } 21 | return ( 22 | 26 | ) 27 | } 28 | 29 | function mapToProps(_, { 30 | phone: { 31 | thirdPartyService, 32 | genericMeeting, 33 | }, 34 | }) { 35 | return { 36 | inviteTitle: thirdPartyService.meetingInviteTitle, 37 | isRCV: genericMeeting.isRCV, 38 | }; 39 | } 40 | 41 | export default withPhone(connect( 42 | mapToProps, 43 | )(MeetingInviteButton)); 44 | -------------------------------------------------------------------------------- /src/modules/CallQueueSettingsUI/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@ringcentral-integration/commons/lib/di'; 2 | import { RcUIModuleV2 } from '@ringcentral-integration/core'; 3 | 4 | @Module({ 5 | name: 'CallQueueSettingsUI', 6 | deps: [ 7 | 'CallQueuePresence', 8 | 'RouterInteraction', 9 | ], 10 | }) 11 | export class CallQueueSettingsUI extends RcUIModuleV2 { 12 | constructor(deps) { 13 | super({ 14 | deps, 15 | }); 16 | } 17 | 18 | getUIProps() { 19 | const { 20 | callQueuePresence, 21 | } = this._deps; 22 | return { 23 | presences: callQueuePresence.presences, 24 | }; 25 | } 26 | 27 | getUIFunctions() { 28 | const { 29 | callQueuePresence, 30 | routerInteraction, 31 | } = this._deps; 32 | return { 33 | sync: async () => { 34 | await callQueuePresence.sync(); 35 | }, 36 | updatePresence: async (queueId, acceptCalls) => { 37 | await callQueuePresence.updatePresence(queueId, acceptCalls); 38 | }, 39 | onBackButtonClick: () => { 40 | routerInteraction.goBack(); 41 | }, 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2021 RingCentral, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 7 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 8 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions 11 | of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 14 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 15 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 16 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 17 | DEALINGS IN THE SOFTWARE. 18 | -------------------------------------------------------------------------------- /src/lib/notification.js: -------------------------------------------------------------------------------- 1 | export default class Notification { 2 | constructor() { 3 | this._enableNotification = false; 4 | this._notification = null; 5 | this._checkOrRequirePermission(); 6 | } 7 | 8 | _checkOrRequirePermission() { 9 | if (!this.nativeAPI) { 10 | console.log('This browser does not support system notifications.'); 11 | return; 12 | } 13 | if (this.hasPermission) { 14 | this._enableNotification = true; 15 | return; 16 | } 17 | if (this.nativeAPI.permission !== 'denied') { 18 | this.nativeAPI.requestPermission(() => { 19 | if (this.hasPermission) { 20 | this._enableNotification = true; 21 | } 22 | }); 23 | } 24 | } 25 | 26 | notify({ title, text, icon, onClick }) { 27 | if (!this._enableNotification) { 28 | return; 29 | } 30 | const n = new window.Notification(title, { body: text, icon }); 31 | n.onclick = onClick; 32 | } 33 | 34 | get hasPermission() { 35 | return this.nativeAPI.permission === 'granted'; 36 | } 37 | 38 | get nativeAPI() { 39 | return window.Notification; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/SaveButton/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import type { FunctionComponent } from 'react'; 3 | import { RcButton } from '@ringcentral/juno'; 4 | import i18n from '@ringcentral-integration/widgets/components/SaveButton/i18n'; 5 | 6 | type SaveButtonProps = { 7 | fullWidth?: boolean; 8 | currentLocale: string; 9 | disabled?: boolean; 10 | onClick?: (...args: any[]) => any; 11 | loading?: boolean; 12 | className?: string; 13 | color?: string; 14 | }; 15 | 16 | export const SaveButton: FunctionComponent = ({ 17 | currentLocale, 18 | disabled = false, 19 | onClick = undefined, 20 | fullWidth = true, 21 | loading = false, 22 | className = undefined, 23 | color = 'action.primary', 24 | }) => { 25 | return ( 26 | 37 | {i18n.getString('save', currentLocale)} 38 | 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /src/components/SettingsPanel/i18n/en-US.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | general: 'General', 3 | theme: "Theme", 4 | region: 'Region', 5 | calling: 'Calling', 6 | logout: 'Logout', 7 | version: 'Version', 8 | settings: 'Settings', 9 | clickToDial: 'Click to Dial', 10 | autoCreateLog: 'Auto-create Call Log', 11 | autoCreateSMSLog: 'Auto-create SMS Log', 12 | autoLogCalls: 'Auto log calls', 13 | autoLogNotes: 'Auto log notes', 14 | autoLogSMS: 'Auto log SMS', 15 | clickToSMS: 'Click to SMS', 16 | logSMSContent: 'Log SMS content', 17 | clickToDialSMS: 'Click to Dial/SMS', 18 | audio: 'Audio', 19 | language: 'Language', 20 | feedback: 'Feedback', 21 | userGuide: "What's New", 22 | quickAccess: 'Quick Access Setting', 23 | report: 'Analytics Report', 24 | shareIdea: 'Share idea', 25 | reportIssue: 'Report issue', 26 | advanced: 'Advanced', 27 | aiAssistant: 'AI Assistant (Beta)', 28 | autoStartAiAssistant: 'Auto-start AI Assistant (Beta)', 29 | callQueuePresenceSetting: 'Manage call queue presence', 30 | status: 'Status', 31 | acceptQueueCalls: 'Accept calls from call queue', 32 | appearance: 'Appearance', 33 | }; 34 | -------------------------------------------------------------------------------- /src/lib/popWindow.ts: -------------------------------------------------------------------------------- 1 | export function popWindow(url: string, id: string, w: number, h: number): WindowProxy | null { 2 | if (url.indexOf('javascript') > 0) { 3 | throw new Error('Invalid window open url'); 4 | } 5 | // Fixes dual-screen position Most browsers Firefox 6 | const dualScreenLeft = 7 | window.screenLeft !== undefined 8 | ? window.screenLeft 9 | : (window.screen as any).left; 10 | const dualScreenTop = 11 | window.screenTop !== undefined 12 | ? window.screenTop 13 | : (window.screen as any).top; 14 | 15 | const width = window.screen.width || window.outerWidth; 16 | const height = window.screen.height || window.innerHeight; 17 | const left = width / 2 - w / 2 + dualScreenLeft; 18 | const top = height / 2 - h / 2 + dualScreenTop; 19 | 20 | const newWindow = window.open( 21 | url, 22 | id, 23 | `scrollbars=yes, width=${w}, height=${h}, top=${top}, left=${left}`, 24 | ); 25 | 26 | // Puts focus on the newWindow 27 | try { 28 | newWindow?.focus(); 29 | } catch (error) { 30 | /* ignore error */ 31 | } 32 | return newWindow; 33 | } 34 | 35 | export default popWindow; 36 | -------------------------------------------------------------------------------- /packages/jsonschema-page/src/Fields/Link.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { 4 | RcLink, 5 | } from '@ringcentral/juno'; 6 | 7 | export function Link({ 8 | schema, 9 | uiSchema = {}, 10 | disabled, 11 | onFocus, 12 | name, 13 | }) { 14 | const variant = uiSchema['ui:variant'] || undefined; 15 | const color = uiSchema['ui:color'] || undefined; 16 | const underline = uiSchema['ui:underline'] || undefined; 17 | const href = uiSchema['ui:href'] || undefined; 18 | const component = uiSchema['ui:bulletedList'] ? 'li' : 'a'; 19 | return ( 20 | { 29 | if (!href) { 30 | // If no href, treat as a button 31 | onFocus(name, '$$clicked'); 32 | return; 33 | } 34 | if (component !== 'a') { 35 | // Open in new tab if not an anchor tag 36 | window.open(href, '_blank'); 37 | } 38 | }} 39 | > 40 | {schema.description} 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /packages/jsonschema-page/src/Templates/DescriptionField.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { RcTypography, styled } from '@ringcentral/juno'; 3 | import { DescriptionFieldProps, FormContextType, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils'; 4 | import { TextWithMarkdown } from '../components/TextWithMarkdown'; 5 | 6 | const StyledTypography = styled(RcTypography)` 7 | a { 8 | font-size: inherit; 9 | line-height: inherit; 10 | } 11 | `; 12 | 13 | export default function DescriptionField< 14 | T = any, 15 | S extends StrictRJSFSchema = RJSFSchema, 16 | F extends FormContextType = any 17 | >(props: DescriptionFieldProps) { 18 | const { id, description, style = {}, uiSchema = {} } = props; 19 | if (description) { 20 | const disabled = uiSchema['ui:disabled'] || false; 21 | const color = disabled ? 'disabled.f02' : 'neutral.f05'; 22 | return ( 23 | 24 | { 25 | typeof description === 'string' ? : description 26 | } 27 | 28 | ); 29 | } 30 | 31 | return null; 32 | } -------------------------------------------------------------------------------- /src/assets/images/help.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/integration/sms-toolbar-button.md: -------------------------------------------------------------------------------- 1 | # SMS toolbar button 2 | 3 | First, register service with `buttonEventPath`: 4 | 5 | ```js 6 | document.querySelector("#rc-widget-adapter-frame").contentWindow.postMessage({ 7 | type: 'rc-adapter-register-third-party-service', 8 | service: { 9 | name: 'TestService', 10 | buttonEventPath: '/button-click', 11 | buttons: [{ 12 | id: 'template', 13 | type: 'smsToolbar', 14 | icon: 'icon_url', 15 | label: 'Template', 16 | }], 17 | } 18 | }, '*'); 19 | ``` 20 | 21 | Add a message event to listen button click event: 22 | 23 | ```js 24 | window.addEventListener('message', function (e) { 25 | var data = e.data; 26 | if (data && data.type === 'rc-post-message-request') { 27 | if (data.path === '/button-click') { 28 | // add your codes here to handle the vcard file download event 29 | console.log(data.body.button); 30 | // response to widget 31 | document.querySelector("#rc-widget-adapter-frame").contentWindow.postMessage({ 32 | type: 'rc-post-message-response', 33 | responseId: data.requestId, 34 | response: { data: 'ok' }, 35 | }, '*'); 36 | } 37 | } 38 | }); 39 | ``` 40 | -------------------------------------------------------------------------------- /src/modules/MeetingInviteModalUI/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@ringcentral-integration/commons/lib/di'; 2 | import { RcUIModuleV2, state, action } from '@ringcentral-integration/core'; 3 | 4 | @Module({ 5 | name: 'MeetingInviteUI', 6 | deps: ['Locale'], 7 | }) 8 | export class MeetingInviteUI extends RcUIModuleV2 { 9 | constructor(deps) { 10 | super({ 11 | deps, 12 | }); 13 | } 14 | 15 | @state 16 | modalShow = false; 17 | 18 | @state 19 | meetingString = ''; 20 | 21 | getUIProps() { 22 | return { 23 | currentLocale: this._deps.locale.currentLocale, 24 | show: this.modalShow, 25 | meetingString: this.meetingString, 26 | } 27 | } 28 | 29 | getUIFunctions() { 30 | return { 31 | onClose: this.closeModal, 32 | }; 33 | } 34 | 35 | @action 36 | private _closeModal() { 37 | this.modalShow = false; 38 | } 39 | 40 | @action 41 | private _showModal(meetingString) { 42 | this.modalShow = true; 43 | this.meetingString = meetingString; 44 | } 45 | 46 | closeModal = () => { 47 | this._closeModal(); 48 | }; 49 | 50 | showModal(meetingInfo) { 51 | this._showModal(meetingInfo.details); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/modules/ContactListUI/index.js: -------------------------------------------------------------------------------- 1 | import { ContactListUI as ContactListUIBase } from '@ringcentral-integration/widgets/modules/ContactListUI'; 2 | import { Module } from '@ringcentral-integration/commons/lib/di'; 3 | 4 | import { computed } from '@ringcentral-integration/core'; 5 | 6 | @Module({ 7 | name: 'ContactListUI', 8 | }) 9 | export class ContactListUI extends ContactListUIBase { 10 | @computed((that) => [ 11 | that.filteredContactsList, 12 | ...Object.values(that._deps.contactSources).map( 13 | (source) => source.contacts, 14 | ), 15 | ]) 16 | get filteredContacts() { 17 | const contactsMap = {}; 18 | this._deps.contactSources.forEach((source) => { 19 | contactsMap[source.sourceName] = {}; 20 | source.contacts.forEach((contact) => { 21 | contactsMap[source.sourceName][contact.id] = contact; 22 | }); 23 | }); 24 | const filteredContactsData = []; 25 | this.filteredContactsList.forEach(([sourceName, id]) => { 26 | // TODO: fix item check in widgets lib 27 | if (contactsMap[sourceName][id]) { 28 | filteredContactsData.push(contactsMap[sourceName][id]); 29 | } 30 | }); 31 | return filteredContactsData; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/containers/MeetingTabContainer/index.tsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import withPhone from '@ringcentral-integration/widgets/lib/withPhone'; 3 | import { SubTabsView } from '../../components/SubTabsView'; 4 | 5 | import i18n from './i18n'; 6 | 7 | function mapToProps(_, { phone, phone: { locale, routerInteraction } }) { 8 | return { 9 | currentPath: routerInteraction.currentPath, 10 | tabs: [ 11 | { 12 | value: '/meeting/home', 13 | label: i18n.getString('home', locale.currentLocale), 14 | }, 15 | { 16 | value: '/meeting/history', 17 | label: i18n.getString('recent', locale.currentLocale), 18 | }, 19 | { 20 | value: '/meeting/history/recordings', 21 | label: i18n.getString('recordings', locale.currentLocale), 22 | } 23 | ] 24 | }; 25 | } 26 | 27 | function mapToFunctions(_, { phone: { routerInteraction } }) { 28 | return { 29 | goTo(path) { 30 | routerInteraction.push(path); 31 | }, 32 | }; 33 | } 34 | 35 | const MeetingTabContainer = withPhone( 36 | connect( 37 | mapToProps, 38 | mapToFunctions, 39 | )(SubTabsView), 40 | ); 41 | 42 | export { mapToProps, mapToFunctions, MeetingTabContainer as default }; 43 | -------------------------------------------------------------------------------- /src/lib/conversationHelper.ts: -------------------------------------------------------------------------------- 1 | import { 2 | messageIsTextMessage, 3 | } from '@ringcentral-integration/commons/lib/messageHelper'; 4 | 5 | export function findExistedConversation(conversations, phoneNumber) { 6 | return conversations.find((conversation) => { 7 | if (!conversation.to || conversation.to.length > 1) { 8 | return false; 9 | } 10 | if (!messageIsTextMessage(conversation)) { 11 | return false; 12 | } 13 | if (conversation.direction === 'Inbound') { 14 | return conversation.from && ( 15 | conversation.from.phoneNumber === phoneNumber || 16 | conversation.from.extensionNumber === phoneNumber 17 | ); 18 | } 19 | return conversation.to.find( 20 | number => ( 21 | number.phoneNumber === phoneNumber || 22 | number.extensionNumber === phoneNumber 23 | ) 24 | ); 25 | }); 26 | } 27 | 28 | export function getConversationPhoneNumber(conversation) { 29 | if (conversation.direction === 'Inbound') { 30 | return conversation.from.phoneNumber || conversation.from.extensionNumber; 31 | } 32 | if (conversation.to.length === 0) { 33 | return null; 34 | } 35 | return conversation.to[0].phoneNumber || conversation.to[0].extensionNumber; 36 | } 37 | -------------------------------------------------------------------------------- /src/modules/WidgetAppsUI/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@ringcentral-integration/commons/lib/di'; 2 | import { RcUIModuleV2 } from '@ringcentral-integration/core'; 3 | 4 | @Module({ 5 | name: 'WidgetAppsUI', 6 | deps: [ 7 | 'ThirdPartyService', 8 | 'Theme', 9 | 'SideDrawerUI', 10 | ], 11 | }) 12 | export class WidgetAppsUI extends RcUIModuleV2 { 13 | constructor(deps) { 14 | super({ 15 | deps, 16 | }); 17 | } 18 | 19 | getUIProps(options) { 20 | const { 21 | thirdPartyService, 22 | } = this._deps; 23 | return { 24 | apps: thirdPartyService.apps, 25 | pinAppIds: thirdPartyService.pinAppIds, 26 | }; 27 | } 28 | 29 | getUIFunctions() { 30 | const { 31 | thirdPartyService, 32 | theme, 33 | sideDrawerUI, 34 | } = this._deps; 35 | return { 36 | onLoadApp: (data) => { 37 | return thirdPartyService.loadAppPage({ 38 | ...data, 39 | theme: theme.themeType, 40 | }); 41 | }, 42 | toggleAppPin: (appId) => { 43 | thirdPartyService.toggleAppPin(appId); 44 | }, 45 | openAppTab: (app, contact) => { 46 | sideDrawerUI.openAppTab(app, contact); 47 | }, 48 | }; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/components/NavigationBar/helper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import type { NavigationButtonIcon } from '@ringcentral-integration/widgets/components/TabNavigationButton'; 4 | import type { 5 | NavigationBarProps, 6 | } from '@ringcentral-integration/widgets/components/NavigationBar/NavigationBar.interface'; 7 | 8 | export function getTabInfo({ 9 | tab, 10 | currentPath, 11 | }: Pick & { 12 | tab: NavigationBarProps['tabs'][number]; 13 | }) { 14 | const active = tab.isActive?.(currentPath) 15 | 16 | const activeAttr = active ? 'true' : ''; 17 | 18 | function getIcon(icon: NavigationButtonIcon | undefined) { 19 | if (!icon) return icon; 20 | 21 | if (React.isValidElement(icon)) { 22 | return React.cloneElement(icon, { 23 | // @ts-expect-error 24 | active: activeAttr, 25 | }); 26 | } 27 | 28 | const Icon = icon; 29 | 30 | return tab.childTabs ? ( 31 | 32 | ) : ( 33 | 34 | ); 35 | } 36 | 37 | const { icon, activeIcon } = tab; 38 | 39 | return { 40 | icon: getIcon(icon), 41 | activeIcon: getIcon(activeIcon), 42 | active, 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /src/components/MeetingItem/styles.scss: -------------------------------------------------------------------------------- 1 | 2 | @import '~@ringcentral-integration/widgets/lib/commonStyles/colors.scss'; 3 | @import '~@ringcentral-integration/widgets/lib/commonStyles/fonts.scss'; 4 | 5 | .root { 6 | display: flex; 7 | flex-direction: column; 8 | background: $snow; 9 | align-items: flex-start; 10 | border-bottom: 1px solid $silver; 11 | color: $ash; 12 | @include secondary-font; 13 | padding: 15px 20px; 14 | position: relative; 15 | } 16 | 17 | .root:hover { 18 | background: $egg; 19 | } 20 | 21 | .clickable { 22 | cursor: pointer; 23 | } 24 | 25 | .item { 26 | margin-bottom: 10px; 27 | display: block; 28 | 29 | &:last-child { 30 | margin-bottom: 0; 31 | } 32 | } 33 | 34 | .subject { 35 | text-decoration: none; 36 | color: $night; 37 | overflow: hidden; 38 | text-overflow: ellipsis; 39 | @include primary-font; 40 | } 41 | 42 | .recording { 43 | display: flex; 44 | line-height: 18px; 45 | flex-direction: row; 46 | cursor: pointer; 47 | } 48 | 49 | .duration { 50 | margin-left: 5px; 51 | } 52 | 53 | .logIcon { 54 | path { 55 | stroke: $primary-color; 56 | } 57 | } 58 | 59 | .logButton { 60 | position: absolute; 61 | bottom: 20px; 62 | right: 20px; 63 | cursor: pointer; 64 | } 65 | -------------------------------------------------------------------------------- /src/components/MainViewPanel/i18n/zh-CN.js: -------------------------------------------------------------------------------- 1 | export default { 2 | dialpadLabel: "拨号键盘", 3 | phoneLabel: "电话", 4 | callsLabel: "通话", 5 | historyLabel: "历史记录", 6 | recordingsLabel: '录音', 7 | messagesLabel: "消息", 8 | inboxLabel: "收件箱", 9 | textLabel: '短信', 10 | voicemailLabel: '语音邮件', 11 | faxLabel: '传真', 12 | moreMenuLabel: "更多", 13 | contactsLabel: "联系人", 14 | meetingLabel: "会议", 15 | glipLabel: '消息', 16 | conferenceLabel: "安排电话会议", 17 | hangoutsLabel: "开始聚会", 18 | hangoutsTitle: "通过召开会议开始聚会", 19 | settingsLabel: "设置", 20 | composeText: "编辑短信", 21 | }; 22 | 23 | // @key: @#@"dialpadLabel"@#@ @source: @#@"Dial Pad"@#@ 24 | // @key: @#@"callsLabel"@#@ @source: @#@"Calls"@#@ 25 | // @key: @#@"historyLabel"@#@ @source: @#@"History"@#@ 26 | // @key: @#@"messagesLabel"@#@ @source: @#@"Messages"@#@ 27 | // @key: @#@"moreMenuLabel"@#@ @source: @#@"More Menu"@#@ 28 | // @key: @#@"contactsLabel"@#@ @source: @#@"Contacts"@#@ 29 | // @key: @#@"meetingLabel"@#@ @source: @#@"Schedule Meeting"@#@ 30 | // @key: @#@"conferenceLabel"@#@ @source: @#@"Schedule Conference"@#@ 31 | // @key: @#@"hangoutsLabel"@#@ @source: @#@"Start Hangouts"@#@ 32 | // @key: @#@"hangoutsTitle"@#@ @source: @#@"Start Hangouts with Conferencing"@#@ 33 | // @key: @#@"settingsLabel"@#@ @source: @#@"Settings"@#@ 34 | -------------------------------------------------------------------------------- /src/components/MainViewPanel/i18n/zh-HK.js: -------------------------------------------------------------------------------- 1 | export default { 2 | dialpadLabel: "撥號鍵盤", 3 | phoneLabel: "電話", 4 | callsLabel: "通話", 5 | historyLabel: "歷史", 6 | recordingsLabel: '錄製檔', 7 | messagesLabel: "訊息", 8 | inboxLabel: "收件匣", 9 | textLabel: '簡訊', 10 | voicemailLabel: '語音訊息', 11 | faxLabel: '傳真', 12 | moreMenuLabel: "更多", 13 | contactsLabel: "通訊錄", 14 | meetingLabel: "會議", 15 | glipLabel: '訊息', 16 | conferenceLabel: "排程電話會議", 17 | hangoutsLabel: "啟動 Hangouts", 18 | hangoutsTitle: "以會議啟動 Hangouts", 19 | settingsLabel: "設定", 20 | composeText: "撰寫簡訊", 21 | }; 22 | 23 | // @key: @#@"dialpadLabel"@#@ @source: @#@"Dial Pad"@#@ 24 | // @key: @#@"callsLabel"@#@ @source: @#@"Calls"@#@ 25 | // @key: @#@"historyLabel"@#@ @source: @#@"History"@#@ 26 | // @key: @#@"messagesLabel"@#@ @source: @#@"Messages"@#@ 27 | // @key: @#@"moreMenuLabel"@#@ @source: @#@"More Menu"@#@ 28 | // @key: @#@"contactsLabel"@#@ @source: @#@"Contacts"@#@ 29 | // @key: @#@"meetingLabel"@#@ @source: @#@"Schedule Meeting"@#@ 30 | // @key: @#@"conferenceLabel"@#@ @source: @#@"Schedule Conference"@#@ 31 | // @key: @#@"hangoutsLabel"@#@ @source: @#@"Start Hangouts"@#@ 32 | // @key: @#@"hangoutsTitle"@#@ @source: @#@"Start Hangouts with Conferencing"@#@ 33 | // @key: @#@"settingsLabel"@#@ @source: @#@"Settings"@#@ 34 | -------------------------------------------------------------------------------- /src/components/MainViewPanel/i18n/zh-TW.js: -------------------------------------------------------------------------------- 1 | export default { 2 | dialpadLabel: "撥號鍵盤", 3 | phoneLabel: "電話", 4 | callsLabel: "通話", 5 | recordingsLabel: '錄製檔', 6 | historyLabel: "歷史", 7 | messagesLabel: "訊息", 8 | inboxLabel: "收件匣", 9 | textLabel: '簡訊', 10 | voicemailLabel: '語音訊息', 11 | faxLabel: '傳真', 12 | moreMenuLabel: "更多", 13 | contactsLabel: "通訊錄", 14 | meetingLabel: "會議", 15 | glipLabel: '訊息', 16 | conferenceLabel: "排程電話會議", 17 | hangoutsLabel: "啟動 Hangouts", 18 | hangoutsTitle: "以會議啟動 Hangouts", 19 | settingsLabel: "設定", 20 | composeText: "撰寫簡訊", 21 | }; 22 | 23 | // @key: @#@"dialpadLabel"@#@ @source: @#@"Dial Pad"@#@ 24 | // @key: @#@"callsLabel"@#@ @source: @#@"Calls"@#@ 25 | // @key: @#@"historyLabel"@#@ @source: @#@"History"@#@ 26 | // @key: @#@"messagesLabel"@#@ @source: @#@"Messages"@#@ 27 | // @key: @#@"moreMenuLabel"@#@ @source: @#@"More Menu"@#@ 28 | // @key: @#@"contactsLabel"@#@ @source: @#@"Contacts"@#@ 29 | // @key: @#@"meetingLabel"@#@ @source: @#@"Schedule Meeting"@#@ 30 | // @key: @#@"conferenceLabel"@#@ @source: @#@"Schedule Conference"@#@ 31 | // @key: @#@"hangoutsLabel"@#@ @source: @#@"Start Hangouts"@#@ 32 | // @key: @#@"hangoutsTitle"@#@ @source: @#@"Start Hangouts with Conferencing"@#@ 33 | // @key: @#@"settingsLabel"@#@ @source: @#@"Settings"@#@ 34 | -------------------------------------------------------------------------------- /docs/integration/call-pop.md: -------------------------------------------------------------------------------- 1 | # Call pop 2 | 3 | This page describes how to implement the call pop feature based on the Embeddable [events](./events.md). 4 | 5 | ## Listen for the active call event 6 | 7 | ```js 8 | window.addEventListener('message', (e) => { 9 | const data = e.data; 10 | if (data) { 11 | switch (data.type) { 12 | case 'rc-active-call-notify': 13 | // only pop call for incoming ringing call 14 | if (data.call.direction === 'Inbound' && data.call.telephonyStatus === 'Ringing') { 15 | // here we popup a Google form pre-fill uri: 16 | const formUri = `https://docs.google.com/forms/d/e/xxxxxxxxx/viewform?usp=pp_url&entry.985526131=${data.call.direction}&entry.1491856435=${data.call.from.phoneNumber}&entry.875629840=${encodeURIComponent(data.call.fromName)}&entry.1789287962=${data.call.to.phoneNumber}&entry.1281736933=${encodeURIComponent(data.call.toName)}`; 17 | window.open(formUri, 'Call form', 'width=600,height=600'); 18 | } 19 | break; 20 | default: 21 | break; 22 | } 23 | } 24 | }); 25 | ``` 26 | 27 | Here we listen to active call event. When there is an incoming call, it will popup a Google Forms pre-fill uri. 28 | Get the online demo [here](https://embbnux.github.io/ringcentral-embeddable-call-pop-demo/). 29 | -------------------------------------------------------------------------------- /src/modules/AlertUI/index.ts: -------------------------------------------------------------------------------- 1 | import { AlertUI as AlertUIBase } from '@ringcentral-integration/widgets/modules/AlertUI'; 2 | import { Module } from '@ringcentral-integration/commons/lib/di'; 3 | import type { 4 | NotificationMessage, 5 | } from '@ringcentral-integration/widgets/components/NotificationPanel/NotificationPanel.interface'; 6 | import { getAlertRenderer } from '../../components/AlertRenderer'; 7 | 8 | @Module({ 9 | name: 'AlertUI', 10 | deps: ['ThirdPartyService'], 11 | }) 12 | export class AlertUI extends AlertUIBase { 13 | constructor(deps) { 14 | super(deps); 15 | this._ignoreModuleReadiness(deps.thirdPartyService); 16 | } 17 | 18 | getUIFunctions(options) { 19 | const functions = super.getUIFunctions(options); 20 | return { 21 | ...functions, 22 | getRenderer: (message: NotificationMessage) => { 23 | const renderer = getAlertRenderer({ 24 | onThirdPartyLinkClick: (id)=> this._deps.thirdPartyService.onClickLinkInAlertDetail(id), 25 | })(message); 26 | if (renderer) { 27 | return renderer; 28 | } 29 | return functions.getRenderer(message); 30 | }, 31 | cancelAutoDismiss: (id) => { 32 | this._deps.alert.update(id, { 33 | ttl: 0, 34 | }); 35 | }, 36 | }; 37 | } 38 | } -------------------------------------------------------------------------------- /src/components/DemoOnlyBanner/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { 4 | RcAlert, 5 | RcIcon, 6 | RcLink, 7 | } from '@ringcentral/juno'; 8 | import { InfoBorder } from '@ringcentral/juno-icon'; 9 | import { styled } from '@ringcentral/juno/foundation'; 10 | 11 | const DemoOnlyWarning = styled(RcAlert)` 12 | padding: 2px 16px 2px 42px!important; 13 | 14 | .RcAlert-message { 15 | font-size: 14px !important; 16 | line-height: 16px; 17 | display: flex; 18 | align-items: center; 19 | justify-content: center; 20 | 21 | a { 22 | line-height: 16px; 23 | height: 16px; 24 | color: inherit; 25 | } 26 | 27 | .MuiAlert-action { 28 | padding-left: 0; 29 | } 30 | } 31 | `; 32 | 33 | export function DemoOnlyBanner({ 34 | show, 35 | onClose, 36 | }) { 37 | if (!show) { 38 | return null; 39 | } 40 | return ( 41 | 45 | FOR DEMO PURPOSES ONLY   46 | 50 | 54 | 55 | 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /src/components/MainViewPanel/i18n/ja-JP.js: -------------------------------------------------------------------------------- 1 | export default { 2 | dialpadLabel: "ダイヤルパッド", 3 | phoneLabel: "電話", 4 | callsLabel: "通話", 5 | historyLabel: "履歴", 6 | messagesLabel: "メッセージ", 7 | recordingsLabel: 'レコーディング', 8 | inboxLabel: "受信トレイ", 9 | textLabel: 'テキスト', 10 | voicemailLabel: 'ボイスメール', 11 | faxLabel: 'FAX', 12 | moreMenuLabel: "その他", 13 | contactsLabel: "連絡先", 14 | meetingLabel: "会議", 15 | conferenceLabel: "会議のスケジュール設定", 16 | hangoutsLabel: "ハングアウトを起動", 17 | hangoutsTitle: "Conferencingでハングアウトを起動", 18 | settingsLabel: "設定", 19 | glipLabel: 'メッセージ', 20 | composeText: "テキストを作成", 21 | }; 22 | 23 | // @key: @#@"dialpadLabel"@#@ @source: @#@"Dial Pad"@#@ 24 | // @key: @#@"callsLabel"@#@ @source: @#@"Calls"@#@ 25 | // @key: @#@"historyLabel"@#@ @source: @#@"History"@#@ 26 | // @key: @#@"messagesLabel"@#@ @source: @#@"Messages"@#@ 27 | // @key: @#@"moreMenuLabel"@#@ @source: @#@"More Menu"@#@ 28 | // @key: @#@"contactsLabel"@#@ @source: @#@"Contacts"@#@ 29 | // @key: @#@"meetingLabel"@#@ @source: @#@"Schedule Meeting"@#@ 30 | // @key: @#@"conferenceLabel"@#@ @source: @#@"Schedule Conference"@#@ 31 | // @key: @#@"hangoutsLabel"@#@ @source: @#@"Start Hangouts"@#@ 32 | // @key: @#@"hangoutsTitle"@#@ @source: @#@"Start Hangouts with Conferencing"@#@ 33 | // @key: @#@"settingsLabel"@#@ @source: @#@"Settings"@#@ 34 | -------------------------------------------------------------------------------- /src/modules/CompanyContacts/index.ts: -------------------------------------------------------------------------------- 1 | import { forEach, reject } from 'ramda'; 2 | import { Module } from '@ringcentral-integration/commons/lib/di'; 3 | import { CompanyContacts as CompanyContactsBase } from '@ringcentral-integration/commons/modules/CompanyContacts'; 4 | 5 | const contactsRegExp = /.*\/directory\/contacts$/; 6 | 7 | @Module({ 8 | name: 'CompanyContacts', 9 | deps: [] 10 | }) 11 | export class CompanyContacts extends CompanyContactsBase { 12 | protected _handleSubscription(message: any) { 13 | if ( 14 | this.ready && 15 | (this._source.disableCache || (this._deps.tabManager?.active ?? true)) && 16 | contactsRegExp.test(message?.event) && 17 | message?.body?.contacts 18 | ) { 19 | let data = this.data ?? []; 20 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 21 | forEach(({ eventType, oldEtag, newEtag, ...contact }) => { 22 | if (eventType === 'Create' || eventType === 'Update') { 23 | data = [...reject((item) => item.id === contact.id, data), contact]; 24 | } else if (eventType === 'Delete') { 25 | data = [...reject((item) => item.id === contact.id, data)]; 26 | } 27 | }, message.body.contacts); 28 | // TODO: fix this issue in widgets lib 29 | this.setCompanyContactsData(data); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/config/styles.md: -------------------------------------------------------------------------------- 1 | # Customize look and feel through CSS 2 | 3 | This is a online [demo](https://embbnux.github.io/ringcentral-web-widget-styles/) built with Game of Thrones Styles. 4 | 5 | Style file is defined here: 6 | 7 | `https://embbnux.github.io/ringcentral-web-widget-styles/GameofThrones/styles.css` 8 | 9 | === "Javascript" 10 | 11 | ```js 12 | 20 | ``` 21 | 22 | === "iframe" 23 | 24 | ```html 25 | 27 | ``` 28 | 29 | !!! hint "See a [live demo](https://apps.ringcentral.com/integration/ringcentral-embeddable/latest/app.html?stylesUri=https://embbnux.github.io/ringcentral-web-widget-styles/GameofThrones/styles.css)" 30 | -------------------------------------------------------------------------------- /src/modules/ThirdPartySettingSectionUI/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@ringcentral-integration/commons/lib/di'; 2 | import { 3 | RcUIModuleV2, 4 | track, 5 | } from '@ringcentral-integration/core'; 6 | import { findSettingItem } from '../ThirdPartyService/helper'; 7 | import { trackEvents } from '../Analytics/trackEvents'; 8 | @Module({ 9 | name: 'ThirdPartySettingSectionUI', 10 | deps: [ 11 | 'RouterInteraction', 12 | 'ThirdPartyService', 13 | ], 14 | }) 15 | export class ThirdPartySettingSectionUI extends RcUIModuleV2 { 16 | constructor(deps) { 17 | super({ 18 | deps, 19 | }); 20 | } 21 | 22 | getUIProps({ 23 | params, 24 | }) { 25 | const { thirdPartyService } = this._deps; 26 | const section = findSettingItem(thirdPartyService.settings, params.sectionId); 27 | return { 28 | section, 29 | }; 30 | } 31 | 32 | getUIFunctions() { 33 | const { 34 | routerInteraction, 35 | } = this._deps; 36 | return { 37 | onSave: (newSection) => { 38 | return this._onSave(newSection); 39 | }, 40 | onBackButtonClick: () => routerInteraction.goBack(), 41 | }; 42 | } 43 | 44 | @track(() => [trackEvents.saveThirdPartySettingSection]) 45 | _onSave(newSection) { 46 | return this._deps.thirdPartyService.onUpdateSetting(newSection); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /docs/integration/click-to-dial.md: -------------------------------------------------------------------------------- 1 | # Working with RingCentral's click-to-dial library 2 | 3 | This is document that show how to implement `Click To Dial` feature with RingCentral C2D library. RingCentral C2D is a library that help developers to implement `Click To Dial` and `Click To SMS` feature, it will scan phone numbers in web page. When users hover on phone number, it will show C2D widget for `Click to Call`. 4 | 5 | ![C2D Screenshot](https://user-images.githubusercontent.com/7036536/51652788-d2627200-1fcb-11e9-8ba3-9e50baeaf8a6.png) 6 | 7 | To implement with RingCentral Embeddable: 8 | 9 | ```html 10 | 11 | 28 | ``` 29 | -------------------------------------------------------------------------------- /src/modules/CallingSettings/index.js: -------------------------------------------------------------------------------- 1 | import { CallingSettings as CallingSettingsBase } from '@ringcentral-integration/commons/modules/CallingSettings'; 2 | import { Module } from '@ringcentral-integration/commons/lib/di'; 3 | 4 | @Module({ 5 | name: 'NewCallingSettings', 6 | deps: [ 7 | { dep: 'CallingSettingsOptions', optional: true } 8 | ] 9 | }) 10 | export class CallingSettings extends CallingSettingsBase { 11 | constructor(deps) { 12 | super(deps); 13 | this._defaultCallWith = deps.callingSettingsOptions.defaultCallWith; 14 | } 15 | 16 | _getDefaultCallWith() { 17 | if (this._defaultCallWith) { 18 | const validatedCallWith = this.callWithOptions.find(c => c === this._defaultCallWith); 19 | if (validatedCallWith) { 20 | return validatedCallWith; 21 | } 22 | } 23 | return this.callWithOptions && this.callWithOptions[0]; 24 | } 25 | 26 | _handleFirstTimeLogin() { 27 | if (!this.timestamp) { 28 | // first time login 29 | const defaultCallWith = this._getDefaultCallWith(); 30 | this.setDataAction({ callWith: defaultCallWith, timestamp: Date.now() }); 31 | if (!this._emergencyCallAvailable) { 32 | this._warningEmergencyCallingNotAvailable(); 33 | } 34 | if (typeof this._onFirstLogin === 'function') { 35 | this._onFirstLogin(); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/modules/Webphone/helper.ts: -------------------------------------------------------------------------------- 1 | import { 2 | normalizeSession as normalizeSessionBase 3 | } from '@ringcentral-integration/commons/modules/Webphone/webphoneHelper'; 4 | 5 | import callDirections from '@ringcentral-integration/commons/enums/callDirections'; 6 | 7 | function getCallQueueName({ direction, headers }) { 8 | if ( 9 | direction === callDirections.outbound || 10 | !headers || 11 | !headers['P-Rc-Api-Call-Info'] || 12 | !headers['P-Rc-Api-Call-Info'][0] || 13 | !headers['P-Rc-Api-Call-Info'][0].raw || 14 | !headers['P-Asserted-Identity'] || 15 | !headers['P-Asserted-Identity'][0] || 16 | !headers['P-Asserted-Identity'][0].raw 17 | ) { 18 | return null; 19 | } 20 | if (headers['P-Rc-Api-Call-Info'][0].raw.indexOf('queue-call') === -1) { 21 | return null; 22 | } 23 | const callInfo = headers['P-Rc-Api-Call-Info'][0].raw.split(';'); 24 | let queueName = callInfo.find((info) => info.indexOf('queueName=') > -1); 25 | if (!queueName) { 26 | return null; 27 | } 28 | queueName = queueName.split('=')[1]; 29 | return `${queueName} - ` 30 | } 31 | 32 | export function normalizeSession(session) { 33 | return { 34 | ...normalizeSessionBase(session), 35 | callQueueName: getCallQueueName({ 36 | direction: session.__rc_direction, 37 | headers: session.request && session.request.headers, 38 | }), 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/MainViewPanel/i18n/en-AU.js: -------------------------------------------------------------------------------- 1 | export default { 2 | dialpadLabel: "Dialpad", 3 | phoneLabel: "Phone", 4 | callsLabel: "Calls", 5 | historyLabel: "History", 6 | recordingsLabel: 'Recordings', 7 | messagesLabel: "Messages", 8 | inboxLabel: "Inbox", 9 | textLabel: 'Text', 10 | voicemailLabel: 'Voicemail', 11 | faxLabel: 'Fax', 12 | moreMenuLabel: "More", 13 | contactsLabel: "Contacts", 14 | meetingLabel: "Meetings", 15 | conferenceLabel: "Schedule conference", 16 | hangoutsLabel: "Start Hangouts", 17 | hangoutsTitle: "Start Hangouts with Conferencing", 18 | settingsLabel: "Settings", 19 | glipLabel: 'Chat', 20 | composeText: "Compose Text", 21 | }; 22 | 23 | // @key: @#@"dialpadLabel"@#@ @source: @#@"Dial Pad"@#@ 24 | // @key: @#@"callsLabel"@#@ @source: @#@"Calls"@#@ 25 | // @key: @#@"historyLabel"@#@ @source: @#@"History"@#@ 26 | // @key: @#@"messagesLabel"@#@ @source: @#@"Messages"@#@ 27 | // @key: @#@"moreMenuLabel"@#@ @source: @#@"More Menu"@#@ 28 | // @key: @#@"contactsLabel"@#@ @source: @#@"Contacts"@#@ 29 | // @key: @#@"meetingLabel"@#@ @source: @#@"Schedule Meeting"@#@ 30 | // @key: @#@"conferenceLabel"@#@ @source: @#@"Schedule Conference"@#@ 31 | // @key: @#@"hangoutsLabel"@#@ @source: @#@"Start Hangouts"@#@ 32 | // @key: @#@"hangoutsTitle"@#@ @source: @#@"Start Hangouts with Conferencing"@#@ 33 | // @key: @#@"settingsLabel"@#@ @source: @#@"Settings"@#@ 34 | -------------------------------------------------------------------------------- /src/components/MainViewPanel/i18n/en-CA.js: -------------------------------------------------------------------------------- 1 | export default { 2 | dialpadLabel: "Dialpad", 3 | phoneLabel: 'Phone', 4 | callsLabel: "Calls", 5 | historyLabel: "History", 6 | recordingsLabel: 'Recordings', 7 | messagesLabel: "Messages", 8 | inboxLabel: 'Inbox', 9 | textLabel: 'Text', 10 | voicemailLabel: 'Voicemail', 11 | faxLabel: 'Fax', 12 | moreMenuLabel: "More", 13 | contactsLabel: "Contacts", 14 | meetingLabel: "Meetings", 15 | conferenceLabel: "Schedule Conference", 16 | hangoutsLabel: "Start Hangouts", 17 | hangoutsTitle: "Start Hangouts with Conferencing", 18 | settingsLabel: "Settings", 19 | glipLabel: 'Chat', 20 | composeText: 'Compose Text', 21 | }; 22 | 23 | // @key: @#@"dialpadLabel"@#@ @source: @#@"Dial Pad"@#@ 24 | // @key: @#@"callsLabel"@#@ @source: @#@"Calls"@#@ 25 | // @key: @#@"historyLabel"@#@ @source: @#@"History"@#@ 26 | // @key: @#@"messagesLabel"@#@ @source: @#@"Messages"@#@ 27 | // @key: @#@"moreMenuLabel"@#@ @source: @#@"More Menu"@#@ 28 | // @key: @#@"contactsLabel"@#@ @source: @#@"Contacts"@#@ 29 | // @key: @#@"meetingLabel"@#@ @source: @#@"Schedule Meeting"@#@ 30 | // @key: @#@"conferenceLabel"@#@ @source: @#@"Schedule Conference"@#@ 31 | // @key: @#@"hangoutsLabel"@#@ @source: @#@"Start Hangouts"@#@ 32 | // @key: @#@"hangoutsTitle"@#@ @source: @#@"Start Hangouts with Conferencing"@#@ 33 | // @key: @#@"settingsLabel"@#@ @source: @#@"Settings"@#@ 34 | -------------------------------------------------------------------------------- /src/components/MainViewPanel/i18n/en-GB.js: -------------------------------------------------------------------------------- 1 | export default { 2 | dialpadLabel: "Dialpad", 3 | phoneLabel: 'Phone', 4 | callsLabel: "Calls", 5 | historyLabel: "History", 6 | recordingsLabel: 'Recordings', 7 | messagesLabel: "Messages", 8 | inboxLabel: 'Inbox', 9 | textLabel: 'Text', 10 | voicemailLabel: 'Voicemail', 11 | faxLabel: 'Fax', 12 | moreMenuLabel: "More", 13 | contactsLabel: "Contacts", 14 | meetingLabel: "Meetings", 15 | conferenceLabel: "Schedule conference", 16 | hangoutsLabel: "Start Hangouts", 17 | hangoutsTitle: "Start Hangouts with Conferencing", 18 | settingsLabel: "Settings", 19 | glipLabel: 'Chat', 20 | composeText: 'Compose Text', 21 | }; 22 | 23 | // @key: @#@"dialpadLabel"@#@ @source: @#@"Dial Pad"@#@ 24 | // @key: @#@"callsLabel"@#@ @source: @#@"Calls"@#@ 25 | // @key: @#@"historyLabel"@#@ @source: @#@"History"@#@ 26 | // @key: @#@"messagesLabel"@#@ @source: @#@"Messages"@#@ 27 | // @key: @#@"moreMenuLabel"@#@ @source: @#@"More Menu"@#@ 28 | // @key: @#@"contactsLabel"@#@ @source: @#@"Contacts"@#@ 29 | // @key: @#@"meetingLabel"@#@ @source: @#@"Schedule Meeting"@#@ 30 | // @key: @#@"conferenceLabel"@#@ @source: @#@"Schedule Conference"@#@ 31 | // @key: @#@"hangoutsLabel"@#@ @source: @#@"Start Hangouts"@#@ 32 | // @key: @#@"hangoutsTitle"@#@ @source: @#@"Start Hangouts with Conferencing"@#@ 33 | // @key: @#@"settingsLabel"@#@ @source: @#@"Settings"@#@ 34 | -------------------------------------------------------------------------------- /src/modules/IncomingCallUI/index.ts: -------------------------------------------------------------------------------- 1 | import { IncomingCallUI as IncomingCallUIBase } from '@ringcentral-integration/widgets/modules/IncomingCallUI'; 2 | import { Module } from '@ringcentral-integration/commons/lib/di'; 3 | 4 | @Module({ 5 | name: 'IncomingCallUI', 6 | }) 7 | export class IncomingCallUI extends IncomingCallUIBase { 8 | // getUIProps(options) { 9 | // const props = super.getUIProps(options); 10 | // // console.log(JSON.stringify(props, null, 2)); 11 | // return { 12 | // ...props, 13 | // }; 14 | // } 15 | 16 | getUIFunctions(options) { 17 | const functions = super.getUIFunctions(options); 18 | return { 19 | ...functions, 20 | ignore: (sessionId) => this._deps.webphone.ignore(sessionId), 21 | updateSessionMatchedContact: (webphoneSessionId, contact) => { 22 | this._deps.webphone.updateSessionMatchedContact(webphoneSessionId, contact); 23 | const session = this._deps.webphone.sessions.find((session) => session.id === webphoneSessionId); 24 | if (session && session.partyData) { 25 | const telephonySessionId = session.partyData.sessionId; 26 | if (telephonySessionId) { 27 | this._deps.contactMatcher?.setCallMatched({ 28 | telephonySessionId, 29 | toEntityId: contact.id 30 | }); 31 | } 32 | } 33 | } 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/components/ConferenceDialerPanel/index.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react'; 2 | import React, { useEffect } from 'react'; 3 | 4 | import { styled } from '@ringcentral/juno/foundation'; 5 | import i18n from '@ringcentral-integration/widgets/components/ConferenceDialerPanel/i18n'; 6 | import type { DialerPanelProps } from '../DialerPanel'; 7 | import DialerPanel from '../DialerPanel'; 8 | import { BackHeader } from '../BackHeader'; 9 | 10 | type ConferenceDialerPanelProps = { 11 | onBack: () => void; 12 | setLastSessionId: () => void; 13 | } & DialerPanelProps; 14 | 15 | const Container = styled.div` 16 | display: flex; 17 | flex-direction: column; 18 | height: 100%; 19 | width: 100%; 20 | `; 21 | 22 | const Content = styled.div` 23 | flex: 1; 24 | padding-top: 30px; 25 | `; 26 | 27 | export const ConferenceDialerPanel: FC = ( 28 | props, 29 | ) => { 30 | const { onBack, setLastSessionId, ...baseProps } = props; 31 | 32 | useEffect(() => { 33 | setLastSessionId(); 34 | // eslint-disable-next-line react-hooks/exhaustive-deps 35 | }, []); 36 | 37 | return ( 38 | 39 | 43 | 44 | 45 | 46 | 47 | ); 48 | }; 49 | -------------------------------------------------------------------------------- /src/components/SmartNotesPanel/SmartNoteApp.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { dynamicLoad, useApp } from '@ringcentral/mfe-react'; 3 | import { styled, useTheme } from '@ringcentral/juno/foundation'; 4 | 5 | const Container = styled.div` 6 | width: 100%; 7 | height: 100%; 8 | 9 | .smart-note-app { 10 | width: 100%; 11 | height: 100%; 12 | 13 | > div { 14 | width: 100%; 15 | height: 100%; 16 | } 17 | } 18 | `; 19 | 20 | export function SmartNoteApp({ 21 | client, 22 | onClose, 23 | onAlert, 24 | smartNoteRemoteEntry, 25 | themeType, 26 | onSave, 27 | showCloseButton, 28 | }) { 29 | const theme = useTheme(); 30 | const SmartNotePlugin = useApp({ 31 | name: 'SmartNotes', 32 | loader: () => { 33 | console.log('loading smart note'); 34 | return dynamicLoad( 35 | '@ringcentral/smart-note-widget/src/bootstrap', 36 | smartNoteRemoteEntry, 37 | ); 38 | }, 39 | attrs: { 40 | className: 'smart-note-app', 41 | }, 42 | bootstrap: async (platform) => {}, 43 | }); 44 | return ( 45 | 46 | 55 | 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /docs/integration/vcard-clicks.md: -------------------------------------------------------------------------------- 1 | # VCard click handler 2 | 3 | !!! info "This feature requires you to [register your app as a service](index.md) first." 4 | 5 | In SMS messages, user can receive vcard (contact) file with MMS. We allow third party to handle the vard attachment download event. For example, when user click vcard file download button, your service will receive the vcard URI, and save the contact into your service. 6 | 7 | First, register service with `vcardHandlerPath`: 8 | 9 | ```js 10 | document.querySelector("#rc-widget-adapter-frame").contentWindow.postMessage({ 11 | type: 'rc-adapter-register-third-party-service', 12 | service: { 13 | name: 'TestService', 14 | vcardHandlerPath: '/vcardHandler', 15 | } 16 | }, '*'); 17 | ``` 18 | 19 | Add a message event to listen vcard click event: 20 | 21 | ```js 22 | window.addEventListener('message', function (e) { 23 | var data = e.data; 24 | if (data && data.type === 'rc-post-message-request') { 25 | if (data.path === '/vcardHandler') { 26 | // add your codes here to handle the vcard file download event 27 | console.log(data.body.vcardUri); 28 | // response to widget 29 | document.querySelector("#rc-widget-adapter-frame").contentWindow.postMessage({ 30 | type: 'rc-post-message-response', 31 | responseId: data.requestId, 32 | response: { data: 'ok' }, 33 | }, '*'); 34 | } 35 | } 36 | }); 37 | ``` 38 | -------------------------------------------------------------------------------- /src/modules/Analytics/trackEvents.ts: -------------------------------------------------------------------------------- 1 | import { ObjectMap } from '@ringcentral-integration/core/lib/ObjectMap'; 2 | 3 | export const trackEvents = ObjectMap.fromObject({ 4 | webRTCCallEnded: 'WebRTC Call Ended', 5 | meetingScheduled: 'Meeting Scheduled', 6 | joinMeeting: 'Join Meeting', 7 | createInstantMeeting: 'Create instant meeting', 8 | saveRingTone: 'Save Ringtone', 9 | viewSmartNotes: 'View smart notes', 10 | startSmartNotes: 'Start smart notes', 11 | enableSmartNotes: 'Enable smart note widget', 12 | disableSmartNotes: 'Disable smart note widget', 13 | enableSmartNotesAutoStart: 'Enable smart note auto start', 14 | disableSmartNotesAutoStart: 'Disable smart note auto start', 15 | saveThirdPartySettingSection: 'Save Third Party Setting Section', 16 | openSideDrawer: 'Open side drawer', 17 | closeSideDrawer: 'Close side drawer', 18 | viewMessageDetails: 'View message details', 19 | viewRecording: 'View recording', 20 | recordInCallControl: 'Call Control: Record/Call control page', 21 | stopRecordInCallControl: 'Call Control: Stop record/Call control page', 22 | muteInCallControl: 'Call Control: Mute/Call control page', 23 | unmuteInCallControl: 'Call Control: Unmute/Call control page', 24 | holdInCallControl: 'Call Control: Hold/Call control page', 25 | unholdInCallControl: 'Call Control: Unhold/Call control page', 26 | updateCallQueuePresence: 'Update call queue presence', 27 | } as const); 28 | -------------------------------------------------------------------------------- /src/components/InitializeAudioBanner/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { 4 | RcAlert, 5 | RcButton, 6 | styled, 7 | palette2, 8 | useAudio, 9 | RcDialerPadSoundsMPEG, 10 | } from '@ringcentral/juno'; 11 | 12 | const StyledAlert = styled(RcAlert)` 13 | &.RcAlert-root { 14 | padding: 0 16px; 15 | background-color: ${palette2('interactive', 'b01')}; 16 | } 17 | 18 | .RcAlert-message { 19 | font-size: 0.875rem; 20 | line-height: 40px; 21 | color: ${palette2('interactive', 'f01')}; 22 | } 23 | 24 | .MuiAlert-action { 25 | padding-left: 0; 26 | margin-right: 0; 27 | } 28 | `; 29 | 30 | export function InitializeAudioBanner({ 31 | onEnableAudio, 32 | }) { 33 | const audio = useAudio((ele) => { 34 | ele.src = RcDialerPadSoundsMPEG['1']; 35 | ele.volume = 0.1; 36 | }); 37 | return ( 38 | { 46 | await audio.play(); 47 | setTimeout(() => { 48 | onEnableAudio(); 49 | }, 1000); 50 | }} 51 | > 52 | Initialize audio 53 | 54 | )} 55 | data-sign="initializeAudioBanner" 56 | > 57 | Audio output is disabled. 58 | 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /src/components/MainViewPanel/i18n/de-DE.js: -------------------------------------------------------------------------------- 1 | export default { 2 | dialpadLabel: "Wählfeld", 3 | phoneLabel: "Telefon", 4 | callsLabel: "Anrufe", 5 | historyLabel: "Verlauf", 6 | recordingsLabel: 'Aufzeichnungen', 7 | messagesLabel: "Nachrichten", 8 | inboxLabel: "Posteingang", 9 | textLabel: 'Textnachr.', 10 | voicemailLabel: 'Voicemail', 11 | faxLabel: 'Fax', 12 | moreMenuLabel: "Mehr“", 13 | contactsLabel: "Kontakte", 14 | meetingLabel: "Besprechungen", 15 | conferenceLabel: "Konferenz planen", 16 | glipLabel: 'Nachricht', 17 | hangoutsLabel: "Hangouts starten", 18 | hangoutsTitle: "Hangouts mit Konferenzfunktion starten", 19 | settingsLabel: "Einstellungen", 20 | composeText: "Text erstellen", 21 | }; 22 | 23 | // @key: @#@"dialpadLabel"@#@ @source: @#@"Dial Pad"@#@ 24 | // @key: @#@"callsLabel"@#@ @source: @#@"Calls"@#@ 25 | // @key: @#@"historyLabel"@#@ @source: @#@"History"@#@ 26 | // @key: @#@"messagesLabel"@#@ @source: @#@"Messages"@#@ 27 | // @key: @#@"moreMenuLabel"@#@ @source: @#@"More Menu"@#@ 28 | // @key: @#@"contactsLabel"@#@ @source: @#@"Contacts"@#@ 29 | // @key: @#@"meetingLabel"@#@ @source: @#@"Schedule Meeting"@#@ 30 | // @key: @#@"conferenceLabel"@#@ @source: @#@"Schedule Conference"@#@ 31 | // @key: @#@"hangoutsLabel"@#@ @source: @#@"Start Hangouts"@#@ 32 | // @key: @#@"hangoutsTitle"@#@ @source: @#@"Start Hangouts with Conferencing"@#@ 33 | // @key: @#@"settingsLabel"@#@ @source: @#@"Settings"@#@ 34 | -------------------------------------------------------------------------------- /docs/integration/log-video-meeting.md: -------------------------------------------------------------------------------- 1 | # Log RingCentral video meeting into your service 2 | 3 | !!! info "This feature requires you to [register your app as a service](index.md) first." 4 | 5 | !!! info "This is only relevant for customers who use RingCentral Video" 6 | 7 | First you need to pass `meetingLoggerPath` and `meetingLoggerTitle` when you register service. 8 | 9 | ```js 10 | document.querySelector("#rc-widget-adapter-frame").contentWindow.postMessage({ 11 | type: 'rc-adapter-register-third-party-service', 12 | service: { 13 | name: 'TestService', 14 | meetingLoggerPath: '/meetingLogger', 15 | meetingLoggerTitle: 'Log to TestService', 16 | } 17 | }, '*'); 18 | ``` 19 | 20 | After registered, you can get a `Log to TestService` in meeting history page. 21 | 22 | Then add a message event to response meeting logger button event: 23 | 24 | ```js 25 | window.addEventListener('message', function (e) { 26 | var data = e.data; 27 | if (data && data.type === 'rc-post-message-request') { 28 | if (data.path === '/meetingLogger') { 29 | // add your codes here to log meeting to your service 30 | console.log(data); 31 | // response to widget 32 | document.querySelector("#rc-widget-adapter-frame").contentWindow.postMessage({ 33 | type: 'rc-post-message-response', 34 | responseId: data.requestId, 35 | response: { data: 'ok' }, 36 | }, '*'); 37 | } 38 | } 39 | }); 40 | ``` 41 | -------------------------------------------------------------------------------- /src/components/MainViewPanel/i18n/fr-FR.js: -------------------------------------------------------------------------------- 1 | export default { 2 | dialpadLabel: "Clavier", 3 | phoneLabel: "Téléphone", 4 | callsLabel: "Appels", 5 | historyLabel: "Historique", 6 | recordingsLabel: 'Enregistrements', 7 | messagesLabel: "Messages", 8 | inboxLabel: "Boîte de réception", 9 | textLabel: 'SMS', 10 | voicemailLabel: 'Messagerie vocale', 11 | faxLabel: 'Fax', 12 | moreMenuLabel: "Plus", 13 | contactsLabel: "Contacts", 14 | meetingLabel: "Réunions", 15 | glipLabel: 'Message', 16 | conferenceLabel: "Planifier une conférence", 17 | hangoutsLabel: "Démarrer Hangouts", 18 | hangoutsTitle: "Commencer une conférence Hangouts", 19 | settingsLabel: "Paramètres", 20 | composeText: "Rédiger un SMS", 21 | }; 22 | 23 | // @key: @#@"dialpadLabel"@#@ @source: @#@"Dial Pad"@#@ 24 | // @key: @#@"callsLabel"@#@ @source: @#@"Calls"@#@ 25 | // @key: @#@"historyLabel"@#@ @source: @#@"History"@#@ 26 | // @key: @#@"messagesLabel"@#@ @source: @#@"Messages"@#@ 27 | // @key: @#@"moreMenuLabel"@#@ @source: @#@"More Menu"@#@ 28 | // @key: @#@"contactsLabel"@#@ @source: @#@"Contacts"@#@ 29 | // @key: @#@"meetingLabel"@#@ @source: @#@"Schedule Meeting"@#@ 30 | // @key: @#@"conferenceLabel"@#@ @source: @#@"Schedule Conference"@#@ 31 | // @key: @#@"hangoutsLabel"@#@ @source: @#@"Start Hangouts"@#@ 32 | // @key: @#@"hangoutsTitle"@#@ @source: @#@"Start Hangouts with Conferencing"@#@ 33 | // @key: @#@"settingsLabel"@#@ @source: @#@"Settings"@#@ 34 | -------------------------------------------------------------------------------- /src/components/MainViewPanel/i18n/it-IT.js: -------------------------------------------------------------------------------- 1 | export default { 2 | dialpadLabel: "Tastierino", 3 | phoneLabel: "Telefono", 4 | callsLabel: "Chiamate", 5 | historyLabel: "Cronologia", 6 | recordingsLabel: 'Registrazioni', 7 | messagesLabel: "Messaggi", 8 | inboxLabel: "In entrata", 9 | textLabel: 'SMS', 10 | voicemailLabel: 'Segreteria telefonica', 11 | faxLabel: 'Fax', 12 | moreMenuLabel: "Altro", 13 | contactsLabel: "Contatti", 14 | meetingLabel: "Riunioni", 15 | conferenceLabel: "Programma conferenza", 16 | hangoutsLabel: "Avvia Hangouts", 17 | hangoutsTitle: "Avvia Hangouts con il servizio di conferenza", 18 | settingsLabel: "Impostazioni", 19 | glipLabel: "Messaggio", 20 | composeText: "Componi SMS", 21 | }; 22 | 23 | // @key: @#@"dialpadLabel"@#@ @source: @#@"Dial Pad"@#@ 24 | // @key: @#@"callsLabel"@#@ @source: @#@"Calls"@#@ 25 | // @key: @#@"historyLabel"@#@ @source: @#@"History"@#@ 26 | // @key: @#@"messagesLabel"@#@ @source: @#@"Messages"@#@ 27 | // @key: @#@"moreMenuLabel"@#@ @source: @#@"More Menu"@#@ 28 | // @key: @#@"contactsLabel"@#@ @source: @#@"Contacts"@#@ 29 | // @key: @#@"meetingLabel"@#@ @source: @#@"Schedule Meeting"@#@ 30 | // @key: @#@"conferenceLabel"@#@ @source: @#@"Schedule Conference"@#@ 31 | // @key: @#@"hangoutsLabel"@#@ @source: @#@"Start Hangouts"@#@ 32 | // @key: @#@"hangoutsTitle"@#@ @source: @#@"Start Hangouts with Conferencing"@#@ 33 | // @key: @#@"settingsLabel"@#@ @source: @#@"Settings"@#@ 34 | -------------------------------------------------------------------------------- /src/components/MainViewPanel/i18n/pt-BR.js: -------------------------------------------------------------------------------- 1 | export default { 2 | dialpadLabel: "Teclado de discagem", 3 | phoneLabel: "Telefone", 4 | callsLabel: "Chamadas", 5 | historyLabel: "Histórico", 6 | recordingsLabel: 'Gravações', 7 | messagesLabel: "Mensagens", 8 | inboxLabel: "Caixa de entrada", 9 | textLabel: 'Texto', 10 | voicemailLabel: 'Caixa postal', 11 | faxLabel: 'Fax', 12 | moreMenuLabel: "Mais", 13 | contactsLabel: "Contatos", 14 | meetingLabel: "Reuniões", 15 | conferenceLabel: "Agendar conferência", 16 | hangoutsLabel: "Iniciar Hangouts", 17 | hangoutsTitle: "Iniciar Hangouts com sistema de conferência", 18 | settingsLabel: "Configurações", 19 | glipLabel: "Mensagem", 20 | composeText: "Criar texto", 21 | }; 22 | 23 | // @key: @#@"dialpadLabel"@#@ @source: @#@"Dial Pad"@#@ 24 | // @key: @#@"callsLabel"@#@ @source: @#@"Calls"@#@ 25 | // @key: @#@"historyLabel"@#@ @source: @#@"History"@#@ 26 | // @key: @#@"messagesLabel"@#@ @source: @#@"Messages"@#@ 27 | // @key: @#@"moreMenuLabel"@#@ @source: @#@"More Menu"@#@ 28 | // @key: @#@"contactsLabel"@#@ @source: @#@"Contacts"@#@ 29 | // @key: @#@"meetingLabel"@#@ @source: @#@"Schedule Meeting"@#@ 30 | // @key: @#@"conferenceLabel"@#@ @source: @#@"Schedule Conference"@#@ 31 | // @key: @#@"hangoutsLabel"@#@ @source: @#@"Start Hangouts"@#@ 32 | // @key: @#@"hangoutsTitle"@#@ @source: @#@"Start Hangouts with Conferencing"@#@ 33 | // @key: @#@"settingsLabel"@#@ @source: @#@"Settings"@#@ 34 | -------------------------------------------------------------------------------- /src/components/MainViewPanel/i18n/es-419.js: -------------------------------------------------------------------------------- 1 | export default { 2 | dialpadLabel: "Teclado telefónico", 3 | phoneLabel: "Teléfono", 4 | callsLabel: "Llamadas", 5 | historyLabel: "Historial", 6 | recordingsLabel: 'Grabaciones', 7 | messagesLabel: "Mensajes", 8 | inboxLabel: "Bandeja de entrada", 9 | textLabel: 'SMS', 10 | voicemailLabel: 'Buzón de voz', 11 | faxLabel: 'Fax', 12 | moreMenuLabel: "Más", 13 | contactsLabel: "Contactos", 14 | meetingLabel: "Reuniones", 15 | conferenceLabel: "Programar una conferencia", 16 | hangoutsLabel: "Iniciar Hangouts", 17 | hangoutsTitle: "Iniciar Hangout con Conferencias", 18 | settingsLabel: "Configuración", 19 | glipLabel: 'Mensaje', 20 | composeText: "Redactar mensaje", 21 | }; 22 | 23 | // @key: @#@"dialpadLabel"@#@ @source: @#@"Dial Pad"@#@ 24 | // @key: @#@"callsLabel"@#@ @source: @#@"Calls"@#@ 25 | // @key: @#@"historyLabel"@#@ @source: @#@"History"@#@ 26 | // @key: @#@"messagesLabel"@#@ @source: @#@"Messages"@#@ 27 | // @key: @#@"moreMenuLabel"@#@ @source: @#@"More Menu"@#@ 28 | // @key: @#@"contactsLabel"@#@ @source: @#@"Contacts"@#@ 29 | // @key: @#@"meetingLabel"@#@ @source: @#@"Schedule Meeting"@#@ 30 | // @key: @#@"conferenceLabel"@#@ @source: @#@"Schedule Conference"@#@ 31 | // @key: @#@"hangoutsLabel"@#@ @source: @#@"Start Hangouts"@#@ 32 | // @key: @#@"hangoutsTitle"@#@ @source: @#@"Start Hangouts with Conferencing"@#@ 33 | // @key: @#@"settingsLabel"@#@ @source: @#@"Settings"@#@ 34 | -------------------------------------------------------------------------------- /src/components/MainViewPanel/i18n/es-ES.js: -------------------------------------------------------------------------------- 1 | export default { 2 | dialpadLabel: "Teclado de marcación", 3 | phoneLabel: "Teléfono", 4 | callsLabel: "Llamadas", 5 | historyLabel: "Historial", 6 | recordingsLabel: 'Grabaciones', 7 | messagesLabel: "Mensajes", 8 | inboxLabel: "Bandeja de entrada", 9 | textLabel: 'Mensaje', 10 | voicemailLabel: 'Buzón de voz', 11 | faxLabel: 'Fax', 12 | moreMenuLabel: "Más", 13 | contactsLabel: "Contactos", 14 | meetingLabel: "Reuniones", 15 | conferenceLabel: "Programar una conferencia", 16 | hangoutsLabel: "Iniciar Hangouts", 17 | hangoutsTitle: "Iniciar Hangout con Conferencias", 18 | settingsLabel: "Configuración", 19 | glipLabel: 'Mensaje', 20 | composeText: "Redactar mensaje" 21 | }; 22 | 23 | // @key: @#@"dialpadLabel"@#@ @source: @#@"Dial Pad"@#@ 24 | // @key: @#@"callsLabel"@#@ @source: @#@"Calls"@#@ 25 | // @key: @#@"historyLabel"@#@ @source: @#@"History"@#@ 26 | // @key: @#@"messagesLabel"@#@ @source: @#@"Messages"@#@ 27 | // @key: @#@"moreMenuLabel"@#@ @source: @#@"More Menu"@#@ 28 | // @key: @#@"contactsLabel"@#@ @source: @#@"Contacts"@#@ 29 | // @key: @#@"meetingLabel"@#@ @source: @#@"Schedule Meeting"@#@ 30 | // @key: @#@"conferenceLabel"@#@ @source: @#@"Schedule Conference"@#@ 31 | // @key: @#@"hangoutsLabel"@#@ @source: @#@"Start Hangouts"@#@ 32 | // @key: @#@"hangoutsTitle"@#@ @source: @#@"Start Hangouts with Conferencing"@#@ 33 | // @key: @#@"settingsLabel"@#@ @source: @#@"Settings"@#@ 34 | -------------------------------------------------------------------------------- /src/components/MainViewPanel/i18n/fr-CA.js: -------------------------------------------------------------------------------- 1 | export default { 2 | dialpadLabel: "Clavier", 3 | phoneLabel: "Téléphone", 4 | callsLabel: "Appels", 5 | historyLabel: "Historique", 6 | recordingsLabel: 'Enregistrements', 7 | messagesLabel: "Messages", 8 | inboxLabel: "Boîte de réception", 9 | textLabel: 'Texto', 10 | voicemailLabel: 'Messagerie vocale', 11 | faxLabel: 'Fax', 12 | moreMenuLabel: "Plus", 13 | contactsLabel: "Contacts", 14 | meetingLabel: "Réunions", 15 | conferenceLabel: "Planifier une téléconférence", 16 | hangoutsLabel: "Démarrer Hangouts", 17 | hangoutsTitle: "Démarrer Hangouts avec la téléconférence", 18 | settingsLabel: "Paramètres", 19 | glipLabel: 'Message', 20 | composeText: "Rédiger le texto", 21 | }; 22 | 23 | // @key: @#@"dialpadLabel"@#@ @source: @#@"Dial Pad"@#@ 24 | // @key: @#@"callsLabel"@#@ @source: @#@"Calls"@#@ 25 | // @key: @#@"historyLabel"@#@ @source: @#@"History"@#@ 26 | // @key: @#@"messagesLabel"@#@ @source: @#@"Messages"@#@ 27 | // @key: @#@"moreMenuLabel"@#@ @source: @#@"More Menu"@#@ 28 | // @key: @#@"contactsLabel"@#@ @source: @#@"Contacts"@#@ 29 | // @key: @#@"meetingLabel"@#@ @source: @#@"Schedule Meeting"@#@ 30 | // @key: @#@"conferenceLabel"@#@ @source: @#@"Schedule Conference"@#@ 31 | // @key: @#@"hangoutsLabel"@#@ @source: @#@"Start Hangouts"@#@ 32 | // @key: @#@"hangoutsTitle"@#@ @source: @#@"Start Hangouts with Conferencing"@#@ 33 | // @key: @#@"settingsLabel"@#@ @source: @#@"Settings"@#@ 34 | -------------------------------------------------------------------------------- /src/components/MeetingHomePanel/JoinDialog.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { 4 | RcButton, 5 | RcDialog, 6 | RcDialogContent, 7 | RcDialogActions, 8 | RcDialogTitle, 9 | RcTextField, 10 | styled, 11 | } from '@ringcentral/juno'; 12 | import i18n from './i18n'; 13 | 14 | const StyledDialog = styled(RcDialog)` 15 | .MuiDialog-paper { 16 | width: calc(100% - 32px); 17 | margin: 16px; 18 | } 19 | `; 20 | 21 | export function JoinDialog({ 22 | open, 23 | onClose, 24 | onJoin, 25 | meetingId, 26 | onMeetingIdChange, 27 | currentLocale, 28 | }) { 29 | return ( 30 | 34 | Join a meeting 35 | 36 | 43 | 44 | 45 | 46 | {i18n.getString('cancel', currentLocale)} 47 | 48 | 52 | {i18n.getString('join', currentLocale)} 53 | 54 | 55 | 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /src/components/SmartNotesPanel/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react'; 2 | import { SmartNoteApp } from './SmartNoteApp'; 3 | 4 | export function SmartNotesPanel({ 5 | smartNoteClient, 6 | smartNoteSession, 7 | smartNoteRemoteEntry, 8 | onClose, 9 | onAlert, 10 | themeType, 11 | onSave, 12 | showCloseButton 13 | }) { 14 | const [session, setSession] = useState(null); 15 | const sessionRef = useRef(session); 16 | 17 | useEffect(() => { 18 | if ( 19 | sessionRef.current && 20 | smartNoteSession && 21 | sessionRef.current.id === smartNoteSession.id 22 | ) { 23 | // avoid re-render when session is the same 24 | return; 25 | } 26 | sessionRef.current = smartNoteSession; 27 | setSession(null); 28 | const timeout = setTimeout(() => { 29 | setSession(smartNoteSession); 30 | }, 50); 31 | return () => { 32 | clearTimeout(timeout); 33 | }; 34 | }, [smartNoteSession]); 35 | 36 | useEffect(() => { 37 | console.log('SmartNotesPanel mounted'); 38 | }, []); 39 | 40 | if (!session || !smartNoteClient) { 41 | return null; 42 | } 43 | 44 | return ( 45 | 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /docs/integration/new-latest-uri.md: -------------------------------------------------------------------------------- 1 | # Migrating from Github Page latest URI 2 | 3 | In previously, we deployed the latest build at Github Page: `https://ringcentral.github.io/ringcentral-embeddable/`. And now the latest build is deployed at `https://apps.ringcentral.com/integration/ringcentral-embeddable/latest/` to have more stable network access. 4 | 5 | To migrate to the new latest URI, you can just replace the old URI with the new one. 6 | 7 | === "Javascript" 8 | 9 | Update adapter js src: 10 | 11 | ```js 12 | 20 | ``` 21 | 22 | === "iframe" 23 | 24 | Update iframe src: 25 | 26 | ```html 27 | 30 | ``` 31 | 32 | Then **add** new redirect URI in your [RingCentral app settings](https://developers.ringcentral.com/) to 33 | 34 | ``` 35 | https://apps.ringcentral.com/integration/ringcentral-embeddable/latest/redirect.html 36 | ``` 37 | 38 | After migrating, user will need to **re-authorize** RingCentral to your app to use the widget as domain changed. 39 | -------------------------------------------------------------------------------- /packages/jsonschema-page/src/Templates/ErrorList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | RcBox as Box, 4 | RcList as List, 5 | RcListItem as ListItem, 6 | RcListItemIcon as ListItemIcon, 7 | RcListItemText as ListItemText, 8 | RcPaper as Paper, 9 | RcTypography as Typography, 10 | RcIcon, 11 | } from '@ringcentral/juno'; 12 | import { InfoBorder as ErrorIcon } from '@ringcentral/juno-icon'; 13 | 14 | import { ErrorListProps, FormContextType, RJSFSchema, StrictRJSFSchema, TranslatableString } from '@rjsf/utils'; 15 | 16 | export default function ErrorList({ 17 | errors, 18 | registry, 19 | }: ErrorListProps) { 20 | const { translateString } = registry; 21 | return ( 22 | 23 | 24 | {translateString(TranslatableString.ErrorsLabel)} 25 | 26 | {errors.map((error, i: number) => { 27 | return ( 28 | 29 | 30 | 34 | 35 | 36 | 37 | ); 38 | })} 39 | 40 | 41 | 42 | ); 43 | } -------------------------------------------------------------------------------- /src/modules/GenericMeeting/index.ts: -------------------------------------------------------------------------------- 1 | import { GenericMeeting as GenericMeetingBase } from '@ringcentral-integration/commons/modules/GenericMeeting'; 2 | import { Module } from '@ringcentral-integration/commons/lib/di'; 3 | 4 | @Module({ 5 | name: 'NewGenericMeeting', 6 | deps: [] 7 | }) 8 | export class GenericMeeting extends GenericMeetingBase { 9 | async fetchHistoryMeetings(params) { 10 | return ( 11 | this._meetingModule && this._meetingModule.fetchHistoryMeetings(params) 12 | ); 13 | } 14 | 15 | async cleanHistoryMeetings() { 16 | return this._meetingModule && this._meetingModule.cleanHistoryMeetings(); 17 | } 18 | 19 | async fetchUpcomingMeetings() { 20 | return this._meetingModule && this._meetingModule.fetchUpcomingMeetings(); 21 | } 22 | 23 | async addThirdPartyProvider(args) { 24 | return this._deps.rcVideo.addThirdPartyProvider(args); 25 | } 26 | 27 | async createInstantMeeting() { 28 | return this._meetingModule && this._meetingModule.createInstantMeeting(); 29 | } 30 | 31 | get historyMeetings() { 32 | return this._meetingModule && this._meetingModule.historyMeetings; 33 | } 34 | 35 | get upcomingMeetings() { 36 | return this._meetingModule && this._meetingModule.upcomingMeetings; 37 | } 38 | 39 | protected get _meetingModule() { 40 | if (this.isRCM) { 41 | return this._deps.meeting; 42 | } 43 | if (this.isRCV) { 44 | return this._deps.rcVideo; 45 | } 46 | return null; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /docs/config/prefix.md: -------------------------------------------------------------------------------- 1 | # Customize Prefix 2 | 3 | We provide default prefix `rc-widget` in the widget. It will used at iframe id prefix and storage key prefix, such as `rc-widget-adapter-frame` and `rc-widget-GlobalStorage-rateLimiterTimestamp`. 4 | 5 | Some developers wants to customize the prefix, so the widget can support to have different user storage data. We provide prefix param to support this feature: 6 | 7 | === "Javascript" 8 | 9 | ```js 10 | 18 | ``` 19 | 20 | === "iframe" 21 | 22 | ```html 23 | 25 | ``` 26 | 27 | After that the widget iframe id will changed to `your_prefix-adapter-frame`. And user data will be storaged at `you_prefix` namespace. 28 | 29 | For implicit grant flow, we use cookie to refresh the token, so it don't support different accounts in same browser in different tabs. If you want to support different accounts in different tabs or domains in same browser, you need to use [authrization code flow](client-id.md). 30 | -------------------------------------------------------------------------------- /packages/jsonschema-page/src/Templates/index.ts: -------------------------------------------------------------------------------- 1 | import { FormContextType, RJSFSchema, TemplatesType } from '@rjsf/utils'; 2 | 3 | import AddButton from './AddButton'; 4 | import ArrayFieldItemTemplate from './ArrayFieldItemTemplate'; 5 | import ArrayFieldTemplate from './ArrayFieldTemplate'; 6 | import BaseInputTemplate from './BaseInputTemplate'; 7 | import DescriptionField from './DescriptionField'; 8 | import ErrorList from './ErrorList'; 9 | import { CopyButton, MoveDownButton, MoveUpButton, RemoveButton } from './IconButton'; 10 | import FieldErrorTemplate from './FieldErrorTemplate'; 11 | import FieldHelpTemplate from './FieldHelpTemplate'; 12 | import FieldTemplate from './FieldTemplate'; 13 | import ObjectFieldTemplate from './ObjectFieldTemplate'; 14 | import SubmitButton from './SubmitButton'; 15 | import TitleField from './TitleField'; 16 | import WrapIfAdditionalTemplate from './WrapIfAdditionalTemplate'; 17 | 18 | export const templates: Partial> = { 19 | ArrayFieldItemTemplate, 20 | ArrayFieldTemplate, 21 | BaseInputTemplate, 22 | ButtonTemplates: { 23 | AddButton, 24 | CopyButton, 25 | MoveDownButton, 26 | MoveUpButton, 27 | RemoveButton, 28 | SubmitButton, 29 | }, 30 | DescriptionFieldTemplate: DescriptionField, 31 | ErrorListTemplate: ErrorList, 32 | FieldErrorTemplate, 33 | FieldHelpTemplate, 34 | FieldTemplate, 35 | ObjectFieldTemplate, 36 | TitleFieldTemplate: TitleField, 37 | WrapIfAdditionalTemplate, 38 | }; 39 | -------------------------------------------------------------------------------- /src/modules/TabManager/index.ts: -------------------------------------------------------------------------------- 1 | import { TabManager as TabManagerBase } from '@ringcentral-integration/commons/modules/TabManager'; 2 | import { Module } from '@ringcentral-integration/commons/lib/di'; 3 | import { action, state } from '@ringcentral-integration/core'; 4 | 5 | @Module({ 6 | name: 'NewTabManager', 7 | deps: [] 8 | }) 9 | export class TabManager extends TabManagerBase { 10 | constructor(options) { 11 | super(options); 12 | // TODO: fix in widgets lib. 1min+ timeout to avoid timer is throttled 13 | this.tabbie._heartBeatExpire = 70000; 14 | window.addEventListener('focus', this._setCurrentTabAsInteracting); 15 | window.addEventListener('visibilitychange', this._setCurrentTabAsInteracting); 16 | this.tabbie.on(this.tabbie.events.event, (event, tabId) => { 17 | if (event !== 'tabInteracting') { 18 | return; 19 | } 20 | if (tabId === this.interactingTabId) { 21 | return; 22 | } 23 | this.setInteractingTabId(tabId); 24 | }); 25 | } 26 | 27 | @state 28 | interactingTabId = null; 29 | 30 | @action 31 | setInteractingTabId(tabId) { 32 | this.interactingTabId = tabId; 33 | } 34 | 35 | _setCurrentTabAsInteracting = () => { 36 | if (document.hidden) { 37 | return; 38 | } 39 | this.setInteractingTabId(this.id); 40 | this.send('tabInteracting', this.id); 41 | } 42 | 43 | get autoMainTab() { 44 | return this._deps.tabManagerOptions.autoMainTab; 45 | } 46 | 47 | get interacting() { 48 | return this.interactingTabId === this.id; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/modules/ContactMatcher/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@ringcentral-integration/commons/lib/di'; 2 | import { ContactMatcher as ContactMatcherBase } from '@ringcentral-integration/commons/modules/ContactMatcher'; 3 | import { action, state, storage, computed } from '@ringcentral-integration/core'; 4 | 5 | @Module({ 6 | name: 'NewContactMatcher', 7 | deps: [] 8 | }) 9 | export class ContactMatcher extends ContactMatcherBase { 10 | @state 11 | manualRefreshNumber = null; 12 | 13 | @action 14 | setManualRefreshNumber(phoneNumber) { 15 | this.manualRefreshNumber = phoneNumber; 16 | } 17 | 18 | @action 19 | resetManualRefreshNumber() { 20 | this.manualRefreshNumber = null; 21 | } 22 | 23 | @storage 24 | @state 25 | callMatchedList = []; 26 | 27 | @action 28 | setCallMatched({ telephonySessionId, toEntityId }) { 29 | const matched = this.callMatchedList.find( 30 | (call) => { 31 | return call.telephonySessionId === telephonySessionId; 32 | } 33 | ); 34 | if (matched) { 35 | matched.toEntityId = toEntityId; 36 | } else { 37 | if (this.callMatchedList.length > 200) { 38 | this.callMatchedList.shift(); 39 | } 40 | this.callMatchedList.push({ 41 | telephonySessionId, 42 | toEntityId, 43 | }); 44 | } 45 | } 46 | 47 | @computed((that: ContactMatcher) => [that.callMatchedList]) 48 | get callMatched() { 49 | return this.callMatchedList.reduce((acc, call) => { 50 | acc[call.telephonySessionId] = call.toEntityId; 51 | return acc; 52 | }, {}); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/config/environment.md: -------------------------------------------------------------------------------- 1 | # Setting your environment 2 | 3 | RingCentral supports two different environments in which applications can run. These two environments are: 4 | 5 | * **Production**. This is the primary environment for normal RingCentral operations. 6 | * **Sandbox**. This is an environment set aside exclusively for developers to build and test applications before making them available in production. 7 | 8 | By default, RingCentral Embeddable's `appServer` configuration parameter points to production. 9 | 10 | === "Javascript" 11 | 12 | ```js 13 | 23 | ``` 24 | 25 | === "iframe" 26 | 27 | ```html 28 | 31 | ``` 32 | 33 | !!! warning "`appKey` and `appSecret` have been renamed" 34 | Starting in version `v1.4.0`, `appKey` has been renamed to `clientId` and `appSecret` has been renamed to `clientSecret`. 35 | -------------------------------------------------------------------------------- /docs/integration/index.md: -------------------------------------------------------------------------------- 1 | # Service integration features 2 | 3 | After integrating the RingCentral Embeddable library into your web application, you can also integrate your own custom service into the CTI as well. This will allow you to associate an icon with [contacts you synchronize](address-book.md) into Embeddable via its API, or display a button to [facilitate authorization](third-party-auth.md) with your service. In a nutshell, anywhere in Embeddable where the library allows you to modify or augment the user interface, requires you to first register your service so the Embeddable can properly indicate what features are native to the CTI, and which ones have been added by a third-party. 4 | 5 | ## Registering your app as a service in Embeddable 6 | 7 | The code below shows how to register your service. When you do so you will choose a name for your service, below we use `TestService`. You will use that exact same name when engaging with the service API features. You will register your service by using the `postMessage` API. 8 | 9 | ```js title="Registering your service via postMessage" 10 | var registered = false; 11 | window.addEventListener('message', function (e) { 12 | const data = e.data; 13 | // Register when widget is loaded 14 | if (data && data.type === 'rc-login-status-notify' && registered === false) { 15 | registered = true; 16 | document.querySelector("#rc-widget-adapter-frame").contentWindow.postMessage({ 17 | type: 'rc-adapter-register-third-party-service', 18 | service: { 19 | name: 'TestService' 20 | } 21 | }, '*'); 22 | } 23 | }); 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /src/modules/CallLogSection/index.js: -------------------------------------------------------------------------------- 1 | import { CallLogSection as CallLogSectionBase } from '@ringcentral-integration/widgets/modules/CallLogSection'; 2 | import { Module } from '@ringcentral-integration/commons/lib/di'; 3 | 4 | /** 5 | * @class CallLogSection 6 | * @extends {CallLogSectionBase} 7 | * @description Call log section popup managing module 8 | */ 9 | @Module({ 10 | deps: ['ThirdPartyService'], 11 | }) 12 | export class CallLogSection extends CallLogSectionBase { 13 | constructor(deps) { 14 | super(deps); 15 | this.addLogHandler({ 16 | logFunction: async (_identify, call, note, input) => { 17 | try { 18 | await this._deps.thirdPartyService.logCall({ 19 | call, 20 | note, 21 | input, 22 | triggerType: 'logForm' 23 | }); 24 | return true; 25 | } catch (e) { 26 | return false; 27 | } 28 | }, 29 | readyCheckFunction: () => this._deps.thirdPartyService.callLoggerRegistered, 30 | onSuccess: () => this.closeLogSection(), 31 | onError: () => null, 32 | onUpdate: () => null, // TODO: update log section 33 | }); 34 | } 35 | 36 | // _showLogSection(identify, call) { 37 | // if (!this.show || identify !== this.currentIdentify) { 38 | // this.store.dispatch({ 39 | // type: this.actionTypes.showLogSection, 40 | // identify, 41 | // call, 42 | // }); 43 | // } 44 | // } 45 | 46 | async handleLogSection(call) { 47 | const { sessionId } = call; 48 | super.handleLogSection(sessionId); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/assets/avaya/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Avaya 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/AudioSettingsPanel/AudioSettingsPanel.interface.ts: -------------------------------------------------------------------------------- 1 | type PickFunctionKeys> = Exclude< 2 | { 3 | [K in keyof T]: Required extends Record any> 4 | ? K 5 | : never; 6 | }[keyof T], 7 | undefined 8 | >; 9 | 10 | export type OmitFunctions> = Omit< 11 | T, 12 | PickFunctionKeys 13 | >; 14 | 15 | export interface AudioSettingsPanelProps { 16 | availableInputDevices: OmitFunctions[]; 17 | availableOutputDevices: OmitFunctions[]; 18 | callVolume: number; 19 | checkUserMedia: () => void | Promise; 20 | className?: string | null; 21 | // TODO: use useLocale when available 22 | currentLocale: string; 23 | dialButtonVolume: number; 24 | inputDeviceDisabled?: boolean; 25 | inputDeviceId: string; 26 | isWebRTC: boolean; 27 | onBackButtonClick: (...args: any) => unknown; 28 | onSave: (...args: any) => unknown; 29 | outputDeviceDisabled?: boolean; 30 | outputDeviceId: string; 31 | ringtoneVolume: number; 32 | showCallVolume?: boolean; 33 | showDialButtonVolume?: boolean; 34 | showRingToneVolume?: boolean; 35 | supportDevices: boolean; 36 | userMedia: boolean; 37 | noiseReductionEnabled: boolean; 38 | showNoiseReductionSetting: boolean; 39 | disableNoiseReductionSetting: boolean; 40 | onNoiseReductionChange: (...args: any) => unknown; 41 | ringtoneDeviceId: string; 42 | onRingtoneDeviceIdChange: (...args: any) => unknown; 43 | showRingtoneAudioSetting: boolean; 44 | gotoRingtoneSettings: (...args: any) => unknown; 45 | } 46 | -------------------------------------------------------------------------------- /src/lib/themes/theme.ts: -------------------------------------------------------------------------------- 1 | import { LiteralUnion, RcThemeInput } from '@ringcentral/juno'; 2 | 3 | import attRich from './attRich'; 4 | import btRich from './btRich'; 5 | import rcJupiterBlue from './rcJupiterBlue'; 6 | import telusRich from './telusRich'; 7 | import avayaCustomized from './avayaCustomized'; 8 | import atosRich from './atosRich'; 9 | import rainbowRich from './rainbowRich'; 10 | import rcDark from './rcDark'; 11 | 12 | // TODO: temporary import all, wait dynamic load way implement 13 | export const brandThemeMapping = { 14 | jupiterBlue: rcJupiterBlue as RcThemeInput, 15 | att: attRich as RcThemeInput, 16 | telus: telusRich as RcThemeInput, 17 | bt: btRich as RcThemeInput, 18 | avaya: avayaCustomized as RcThemeInput, 19 | atos: atosRich as RcThemeInput, 20 | rainbow: rainbowRich as RcThemeInput, 21 | rcDark: rcDark as RcThemeInput, 22 | } as const; 23 | 24 | export type BrandTheme = keyof typeof brandThemeMapping | 'rc'; 25 | 26 | const innerGetBrandTheme = ( 27 | brand: LiteralUnion = 'rc', 28 | defaultTheme: RcThemeInput, 29 | ): RcThemeInput => { 30 | if (brand === 'rc') { 31 | return defaultTheme; 32 | } 33 | 34 | return (brandThemeMapping as any)[brand] || defaultTheme; 35 | }; 36 | 37 | export const getBrandThemeWithJupiterBlue = ( 38 | brand: LiteralUnion = 'rc', 39 | ): RcThemeInput => { 40 | return innerGetBrandTheme(brand, brandThemeMapping.jupiterBlue); 41 | }; 42 | 43 | export const getBrandDarkTheme = ( 44 | brand: LiteralUnion = 'rc', 45 | ): RcThemeInput => { 46 | return innerGetBrandTheme(brand, brandThemeMapping.rcDark); 47 | }; 48 | -------------------------------------------------------------------------------- /src/modules/DateTimeFormat/index.ts: -------------------------------------------------------------------------------- 1 | import { DateTimeFormat as DateTimeFormatBase } from '@ringcentral-integration/commons/modules/DateTimeFormat'; 2 | import { Module } from '@ringcentral-integration/commons/lib/di'; 3 | import getIntlDateTimeFormatter, { 4 | DEFAULT_DATE_TIME_OPTIONS, 5 | DEFAULT_TIME_OPTIONS, 6 | DEFAULT_DATE_OPTIONS, 7 | } from '@ringcentral-integration/commons/lib/getIntlDateTimeFormatter'; 8 | 9 | // detect if enable 24-hour format (cross-browser) 10 | function isUserLocale24Hour() { 11 | try { 12 | const { hour12 } = new Intl.DateTimeFormat(navigator.language, { hour: 'numeric' }).resolvedOptions(); 13 | if (typeof hour12 === 'boolean') { 14 | return hour12 === false; 15 | } 16 | } catch (e) { 17 | // ignore and use fallback 18 | } 19 | try { 20 | const formatted = new Intl.DateTimeFormat(navigator.language, { hour: 'numeric' }).format(new Date()); 21 | return !/\s?(AM|PM)/i.test(formatted); 22 | } catch (e) { 23 | // ignore and use fallback 24 | } 25 | return true; 26 | } 27 | 28 | @Module({ 29 | name: 'NewDateTimeFormat', 30 | deps: [] 31 | }) 32 | export class DateTimeFormat extends DateTimeFormatBase { 33 | private _defaultFormatter: any; 34 | constructor(deps) { 35 | super(deps); 36 | const hour12 = !isUserLocale24Hour(); 37 | this._defaultFormatter = getIntlDateTimeFormatter({ 38 | dateTimeOptions: { 39 | ...DEFAULT_DATE_TIME_OPTIONS, 40 | hour12, 41 | }, 42 | dateOptions: DEFAULT_DATE_OPTIONS, 43 | timeOptions: { 44 | ...DEFAULT_TIME_OPTIONS, 45 | hour12, 46 | }, 47 | }); 48 | } 49 | } -------------------------------------------------------------------------------- /src/modules/ThemeSettingUI/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@ringcentral-integration/commons/lib/di'; 2 | import { RcUIModuleV2 } from '@ringcentral-integration/core'; 3 | 4 | @Module({ 5 | name: 'ThemeSettingUI', 6 | deps: [ 7 | 'Theme', 8 | 'RouterInteraction', 9 | ], 10 | }) 11 | export class ThemeSettingUI extends RcUIModuleV2 { 12 | constructor(deps) { 13 | super({ 14 | deps, 15 | }); 16 | } 17 | 18 | getUIProps() { 19 | const { theme } = this._deps; 20 | const value = theme.isAutoMode ? 'auto' : theme.themeType; 21 | return { 22 | section: { 23 | id: 'theme', 24 | name: 'Theme', 25 | type: 'section', 26 | items: [ 27 | { 28 | id: 'theme', 29 | name: 'Set color theme to', 30 | type: 'option', 31 | value, 32 | options: [ 33 | { id: 'auto', name: 'From system settings' }, 34 | { id: 'light', name: 'Light' }, 35 | { id: 'dark', name: 'Dark' }, 36 | ], 37 | }, 38 | ], 39 | } 40 | }; 41 | } 42 | 43 | getUIFunctions() { 44 | return { 45 | onSave: (newSetting) => { 46 | if (newSetting.items[0].value === 'auto') { 47 | this._deps.theme.setAutoMode(true); 48 | this._deps.theme.setThemeTypeSystem(); 49 | return; 50 | } 51 | this._deps.theme.setAutoMode(false); 52 | this._deps.theme.setThemeType(newSetting.items[0].value); 53 | }, 54 | onBackButtonClick: () => { 55 | this._deps.routerInteraction.goBack(); 56 | } 57 | }; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /docs/extra.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --md-primary-fg-color: #002755; 3 | } 4 | 5 | .md-banner { 6 | text-align: center; 7 | font-weight: bold; 8 | background-color: rgb(255, 122, 0); 9 | } 10 | .md-banner .upgrade-button { 11 | display: inline-block; 12 | border-radius: 20px; 13 | padding: 3px 10px; 14 | background: white; 15 | margin-left: 10px; 16 | } 17 | .md-banner .md-typeset a { 18 | color: rgb(122, 36, 0); 19 | } 20 | .md-typeset a { 21 | color: #066fac; 22 | } 23 | 24 | #config-table td:first-child { 25 | text-wrap: nowrap; 26 | } 27 | 28 | .badge { 29 | font-size: .85em; 30 | color: rgb(64, 81, 181) !important; 31 | line-height: 0.9rem; 32 | } 33 | .badge .title { 34 | border-top-left-radius: 0.1rem; 35 | border-bottom-left-radius: 0.1rem; 36 | background-color: var(--md-accent-fg-color--transparent) !important; 37 | color: rgb(64, 81, 181) !important; 38 | padding: 0rem; 39 | } 40 | 41 | .badge .value { 42 | border-top-right-radius: 0.1rem; 43 | border-bottom-right-radius: 0.1rem; 44 | box-shadow: 0 0 0 1px inset var(--md-accent-fg-color--transparent); 45 | padding: 0.2rem 0.3rem; 46 | background-color: white !important; 47 | color: rgb(64, 81, 181) !important; 48 | } 49 | 50 | .badge.version .title::before { 51 | content: "\f02b"; 52 | } 53 | 54 | .md-typeset .mdx-badge { 55 | font-size: .85em; 56 | } 57 | 58 | .md-typeset .mdx-badge__icon { 59 | background: var(--md-accent-fg-color--transparent); 60 | padding: 0.2rem; 61 | } 62 | 63 | .md-typeset .mdx-badge__text { 64 | box-shadow: 0 0 0 1px inset var(--md-accent-fg-color--transparent); 65 | padding: 0.2rem 0.3rem; 66 | } 67 | -------------------------------------------------------------------------------- /src/components/BackHeader/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import type { ReactNode } from 'react'; 3 | import { styled, palette2 } from '@ringcentral/juno/foundation'; 4 | import { RcIconButton, RcTypography } from '@ringcentral/juno'; 5 | import { Previous } from '@ringcentral/juno-icon'; 6 | 7 | const BackButton = styled(RcIconButton)` 8 | position: absolute; 9 | left: 6px; 10 | top: 0; 11 | `; 12 | 13 | const Header = styled.div` 14 | position: relative; 15 | display: block; 16 | padding: 0 34px; 17 | height: 40px; 18 | min-height: 32px; 19 | text-align: center; 20 | border-bottom: 1px solid ${palette2('neutral', 'l02')}; 21 | background-color: ${palette2('nav', 'b01')}; 22 | `; 23 | 24 | const BackButtonLabel = styled(RcTypography)` 25 | position: absolute; 26 | left: 45px; 27 | top: 8px; 28 | `; 29 | 30 | interface BackHeaderProps { 31 | onBack: () => void; 32 | children?: ReactNode; 33 | label?: string; 34 | hideBackButton?: boolean; 35 | } 36 | 37 | export function BackHeader({ 38 | onBack, 39 | children = null, 40 | label = undefined, 41 | hideBackButton = false, 42 | }: BackHeaderProps) { 43 | return ( 44 |
45 | { 46 | hideBackButton ? null : ( 47 | 52 | ) 53 | } 54 | { 55 | label && ( 56 | 57 | {label} 58 | 59 | ) 60 | } 61 | { 62 | children 63 | } 64 |
65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /packages/jsonschema-page/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withTheme, ThemeProps } from '@rjsf/core'; 3 | import { widgets } from './Widgets'; 4 | import { templates } from './Templates'; 5 | import { fields } from './Fields'; 6 | import { Validator } from './validator'; 7 | import { TextWithMarkdown } from './components/TextWithMarkdown'; 8 | import { ActionMenu } from './components/ActionMenu'; 9 | 10 | const theme: ThemeProps = { widgets, templates }; 11 | 12 | const Form = withTheme(theme); 13 | const validator = new Validator(); 14 | 15 | export { TextWithMarkdown, ActionMenu }; 16 | export const JSONSchemaPage = ({ 17 | schema, 18 | uiSchema = {}, 19 | formData, 20 | onFormDataChange, 21 | onButtonClick, 22 | hiddenSubmitButton = true, 23 | onSubmit, 24 | }) => { 25 | let formUISchema = uiSchema; 26 | if (hiddenSubmitButton) { 27 | formUISchema = { 28 | ...formUISchema, 29 | 'ui:submitButtonOptions': { 30 | norender: true, 31 | }, 32 | }; 33 | } else if (formUISchema['submitButtonOptions']) { 34 | formUISchema = { 35 | ...formUISchema, 36 | 'ui:submitButtonOptions': { 37 | ...formUISchema['submitButtonOptions'], 38 | }, 39 | }; 40 | } 41 | return ( 42 |
{ 47 | onFormDataChange(e.formData); 48 | }} 49 | uiSchema={formUISchema} 50 | fields={fields} 51 | onFocus={(name, value) => { 52 | if (value === '$$clicked') { 53 | onButtonClick(name); 54 | } 55 | }} 56 | onSubmit={onSubmit} 57 | /> 58 | ); 59 | } 60 | --------------------------------------------------------------------------------