diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js
index 3fec00201..1deeb4376 100644
--- a/app/actions/actionsTypes.js
+++ b/app/actions/actionsTypes.js
@@ -66,3 +66,4 @@ export const INVITE_LINKS = createRequestTypes('INVITE_LINKS', [
]);
export const SETTINGS = createRequestTypes('SETTINGS', ['CLEAR', 'ADD']);
export const APP_STATE = createRequestTypes('APP_STATE', ['FOREGROUND', 'BACKGROUND']);
+export const ENTERPRISE_MODULES = createRequestTypes('ENTERPRISE_MODULES', ['CLEAR', 'SET']);
diff --git a/app/actions/enterpriseModules.js b/app/actions/enterpriseModules.js
new file mode 100644
index 000000000..74b097873
--- /dev/null
+++ b/app/actions/enterpriseModules.js
@@ -0,0 +1,14 @@
+import { ENTERPRISE_MODULES } from './actionsTypes';
+
+export function setEnterpriseModules(modules) {
+ return {
+ type: ENTERPRISE_MODULES.SET,
+ payload: modules
+ };
+}
+
+export function clearEnterpriseModules() {
+ return {
+ type: ENTERPRISE_MODULES.CLEAR
+ };
+}
diff --git a/app/i18n/locales/en.js b/app/i18n/locales/en.js
index 061a6228d..ef35a18a7 100644
--- a/app/i18n/locales/en.js
+++ b/app/i18n/locales/en.js
@@ -339,6 +339,7 @@ export default {
Offline: 'Offline',
Oops: 'Oops!',
Omnichannel: 'Omnichannel',
+ Omnichannel_enable_alert: 'You\'re not available on Omnichannel. Would you like to be available?',
Onboarding_description: 'A workspace is your team or organization’s space to collaborate. Ask the workspace admin for address to join or create one for your team.',
Onboarding_join_workspace: 'Join a workspace',
Onboarding_subtitle: 'Beyond Team Collaboration',
diff --git a/app/i18n/locales/pt-BR.js b/app/i18n/locales/pt-BR.js
index 84941b09d..792498fc0 100644
--- a/app/i18n/locales/pt-BR.js
+++ b/app/i18n/locales/pt-BR.js
@@ -314,6 +314,8 @@ export default {
Not_RC_Server: 'Este não é um servidor Rocket.Chat.\n{{contact}}',
No_available_agents_to_transfer: 'Nenhum agente disponível para transferência',
Offline: 'Offline',
+ Omnichannel: 'Omnichannel',
+ Omnichannel_enable_alert: 'Você não está disponível no Omnichannel. Você quer ficar disponível?',
Oops: 'Ops!',
Onboarding_description: 'Workspace é o espaço de colaboração do seu time ou organização. Peça um convite ou o endereço ao seu administrador ou crie uma workspace para o seu time.',
Onboarding_join_workspace: 'Entre numa workspace',
diff --git a/app/lib/database/model/Server.js b/app/lib/database/model/Server.js
index d30b3a3f4..5e98ea954 100644
--- a/app/lib/database/model/Server.js
+++ b/app/lib/database/model/Server.js
@@ -27,4 +27,6 @@ export default class Server extends Model {
@field('biometry') biometry;
@field('unique_id') uniqueID;
+
+ @field('enterprise_modules') enterpriseModules;
}
diff --git a/app/lib/database/model/serversMigrations.js b/app/lib/database/model/serversMigrations.js
index 8d74b0434..86995e5c4 100644
--- a/app/lib/database/model/serversMigrations.js
+++ b/app/lib/database/model/serversMigrations.js
@@ -37,6 +37,17 @@ export default schemaMigrations({
]
})
]
+ },
+ {
+ toVersion: 6,
+ steps: [
+ addColumns({
+ table: 'servers',
+ columns: [
+ { name: 'enterprise_modules', type: 'string', isOptional: true }
+ ]
+ })
+ ]
}
]
});
diff --git a/app/lib/database/schema/servers.js b/app/lib/database/schema/servers.js
index b02859e10..11c115ade 100644
--- a/app/lib/database/schema/servers.js
+++ b/app/lib/database/schema/servers.js
@@ -1,7 +1,7 @@
import { appSchema, tableSchema } from '@nozbe/watermelondb';
export default appSchema({
- version: 5,
+ version: 6,
tables: [
tableSchema({
name: 'users',
@@ -29,7 +29,8 @@ export default appSchema({
{ name: 'auto_lock', type: 'boolean', isOptional: true },
{ name: 'auto_lock_time', type: 'number', isOptional: true },
{ name: 'biometry', type: 'boolean', isOptional: true },
- { name: 'unique_id', type: 'string', isOptional: true }
+ { name: 'unique_id', type: 'string', isOptional: true },
+ { name: 'enterprise_modules', type: 'string', isOptional: true }
]
})
]
diff --git a/app/lib/methods/enterpriseModules.js b/app/lib/methods/enterpriseModules.js
new file mode 100644
index 000000000..60600afae
--- /dev/null
+++ b/app/lib/methods/enterpriseModules.js
@@ -0,0 +1,63 @@
+import semver from 'semver';
+
+import reduxStore from '../createStore';
+import database from '../database';
+import log from '../../utils/log';
+import { setEnterpriseModules as setEnterpriseModulesAction, clearEnterpriseModules } from '../../actions/enterpriseModules';
+
+export const LICENSE_OMNICHANNEL_MOBILE_ENTERPRISE = 'omnichannel-mobile-enterprise';
+export const LICENSE_LIVECHAT_ENTERPRISE = 'livechat-enterprise';
+
+export async function setEnterpriseModules() {
+ try {
+ const { server: serverId } = reduxStore.getState().server;
+ const serversDB = database.servers;
+ const serversCollection = serversDB.collections.get('servers');
+ const server = await serversCollection.find(serverId);
+ if (server.enterpriseModules) {
+ reduxStore.dispatch(setEnterpriseModulesAction(server.enterpriseModules.split(',')));
+ return;
+ }
+ reduxStore.dispatch(clearEnterpriseModules());
+ } catch (e) {
+ log(e);
+ }
+}
+
+export function getEnterpriseModules() {
+ return new Promise(async(resolve) => {
+ try {
+ const { version: serverVersion, server: serverId } = reduxStore.getState().server;
+ if (serverVersion && semver.gte(semver.coerce(serverVersion), '3.1.0')) {
+ // RC 3.1.0
+ const enterpriseModules = await this.methodCallWrapper('license:getModules');
+ if (enterpriseModules) {
+ const serversDB = database.servers;
+ const serversCollection = serversDB.collections.get('servers');
+ const server = await serversCollection.find(serverId);
+ await serversDB.action(async() => {
+ await server.update((s) => {
+ s.enterpriseModules = enterpriseModules.join(',');
+ });
+ });
+ reduxStore.dispatch(setEnterpriseModulesAction(enterpriseModules));
+ return resolve();
+ }
+ }
+ reduxStore.dispatch(clearEnterpriseModules());
+ } catch (e) {
+ log(e);
+ }
+ return resolve();
+ });
+}
+
+export function hasLicense(module) {
+ const { enterpriseModules } = reduxStore.getState();
+ return enterpriseModules.includes(module);
+}
+
+export function isOmnichannelModuleAvailable() {
+ const { enterpriseModules } = reduxStore.getState();
+ return [LICENSE_OMNICHANNEL_MOBILE_ENTERPRISE, LICENSE_LIVECHAT_ENTERPRISE].some(module => enterpriseModules.includes(module));
+}
diff --git a/app/lib/methods/subscriptions/rooms.js b/app/lib/methods/subscriptions/rooms.js
index 52e474f14..fc76c11ae 100644
--- a/app/lib/methods/subscriptions/rooms.js
+++ b/app/lib/methods/subscriptions/rooms.js
@@ -244,7 +244,9 @@ export default function subscribeRooms() {
const [, ev] = ddpMessage.fields.eventName.split('/');
if (/userData/.test(ev)) {
const [{ diff }] = ddpMessage.fields.args;
- store.dispatch(setUser({ statusLivechat: diff?.statusLivechat }));
+ if (diff?.statusLivechat) {
+ store.dispatch(setUser({ statusLivechat: diff.statusLivechat }));
+ }
}
if (/subscriptions/.test(ev)) {
if (type === 'removed') {
diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js
index 3ac30ebf6..33fb6990a 100644
--- a/app/lib/rocketchat.js
+++ b/app/lib/rocketchat.js
@@ -29,6 +29,9 @@ import getSettings, { getLoginSettings, setSettings } from './methods/getSetting
import getRooms from './methods/getRooms';
import getPermissions from './methods/getPermissions';
import { getCustomEmojis, setCustomEmojis } from './methods/getCustomEmojis';
+import {
+ getEnterpriseModules, setEnterpriseModules, hasLicense, isOmnichannelModuleAvailable
+} from './methods/enterpriseModules';
import getSlashCommands from './methods/getSlashCommands';
import getRoles from './methods/getRoles';
import canOpenRoom from './methods/canOpenRoom';
@@ -519,6 +522,7 @@ const RocketChat = {
} else if (!filterUsers && filterRooms) {
data = data.filter(item => item.t !== 'd' || RocketChat.isGroupChat(item));
}
+
data = data.slice(0, 7);
data = data.map((sub) => {
@@ -620,6 +624,10 @@ const RocketChat = {
getPermissions,
getCustomEmojis,
setCustomEmojis,
+ getEnterpriseModules,
+ setEnterpriseModules,
+ hasLicense,
+ isOmnichannelModuleAvailable,
getSlashCommands,
getRoles,
parseSettings: settings => settings.reduce((ret, item) => {
diff --git a/app/reducers/enterpriseModules.js b/app/reducers/enterpriseModules.js
new file mode 100644
index 000000000..2f1a7ac9a
--- /dev/null
+++ b/app/reducers/enterpriseModules.js
@@ -0,0 +1,14 @@
+import { ENTERPRISE_MODULES } from '../actions/actionsTypes';
+
+const initialState = [];
+
+export default (state = initialState, action) => {
+ switch (action.type) {
+ case ENTERPRISE_MODULES.SET:
+ return action.payload;
+ case ENTERPRISE_MODULES.CLEAR:
+ return initialState;
+ default:
+ return state;
+ }
+};
diff --git a/app/reducers/index.js b/app/reducers/index.js
index 968254ffd..05bcb534f 100644
--- a/app/reducers/index.js
+++ b/app/reducers/index.js
@@ -17,6 +17,7 @@ import usersTyping from './usersTyping';
import inviteLinks from './inviteLinks';
import createDiscussion from './createDiscussion';
import inquiry from './inquiry';
+import enterpriseModules from './enterpriseModules';
export default combineReducers({
settings,
@@ -36,5 +37,6 @@ export default combineReducers({
usersTyping,
inviteLinks,
createDiscussion,
- inquiry
+ inquiry,
+ enterpriseModules
});
diff --git a/app/sagas/login.js b/app/sagas/login.js
index 4bc9aeae6..1b40c84b1 100644
--- a/app/sagas/login.js
+++ b/app/sagas/login.js
@@ -14,7 +14,7 @@ import {
loginFailure, loginSuccess, setUser, logout
} from '../actions/login';
import { roomsRequest } from '../actions/rooms';
-import { inquiryRequest } from '../actions/inquiry';
+import { inquiryRequest, inquiryReset } from '../actions/inquiry';
import { toMomentLocale } from '../utils/moment';
import RocketChat from '../lib/rocketchat';
import log, { logEvent, events } from '../utils/log';
@@ -85,6 +85,14 @@ const fetchUsersPresence = function* fetchUserPresence() {
RocketChat.subscribeUsersPresence();
};
+const fetchEnterpriseModules = function* fetchEnterpriseModules({ user }) {
+ yield RocketChat.getEnterpriseModules();
+
+ if (user && user.statusLivechat === 'available' && RocketChat.isOmnichannelModuleAvailable()) {
+ yield put(inquiryRequest());
+ }
+};
+
const handleLoginSuccess = function* handleLoginSuccess({ user }) {
try {
const adding = yield select(state => state.server.adding);
@@ -94,13 +102,13 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
const server = yield select(getServer);
yield put(roomsRequest());
- yield put(inquiryRequest());
yield fork(fetchPermissions);
yield fork(fetchCustomEmojis);
yield fork(fetchRoles);
yield fork(fetchSlashCommands);
yield fork(registerPushToken);
yield fork(fetchUsersPresence);
+ yield fork(fetchEnterpriseModules, { user });
I18n.locale = user.language;
moment.locale(toMomentLocale(user.language));
@@ -210,8 +218,12 @@ const handleSetUser = function* handleSetUser({ user }) {
yield put(setActiveUsers({ [userId]: user }));
}
- if (user && user.statusLivechat) {
- yield put(inquiryRequest());
+ if (user?.statusLivechat && RocketChat.isOmnichannelModuleAvailable()) {
+ if (user.statusLivechat === 'available') {
+ yield put(inquiryRequest());
+ } else {
+ yield put(inquiryReset());
+ }
}
};
diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js
index c3b883c31..81b3f7548 100644
--- a/app/sagas/selectServer.js
+++ b/app/sagas/selectServer.js
@@ -109,6 +109,7 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
// and block the selectServerSuccess raising multiples errors
RocketChat.setSettings();
RocketChat.setCustomEmojis();
+ RocketChat.setEnterpriseModules();
let serverInfo;
if (fetchVersion) {
diff --git a/app/views/RoomsListView/ListHeader/OmnichannelStatus.js b/app/views/RoomsListView/ListHeader/OmnichannelStatus.js
new file mode 100644
index 000000000..cc3df2ad9
--- /dev/null
+++ b/app/views/RoomsListView/ListHeader/OmnichannelStatus.js
@@ -0,0 +1,83 @@
+import React, { memo, useState, useEffect } from 'react';
+import {
+ View, Text, StyleSheet, Switch
+} from 'react-native';
+import PropTypes from 'prop-types';
+
+import Touch from '../../../utils/touch';
+import { CustomIcon } from '../../../lib/Icons';
+import I18n from '../../../i18n';
+import styles from '../styles';
+import { themes, SWITCH_TRACK_COLOR } from '../../../constants/colors';
+import { withTheme } from '../../../theme';
+import UnreadBadge from '../../../presentation/UnreadBadge';
+import RocketChat from '../../../lib/rocketchat';
+
+const OmnichannelStatus = memo(({
+ searching, goQueue, theme, queueSize, inquiryEnabled, user
+}) => {
+ if (searching > 0 || !(RocketChat.isOmnichannelModuleAvailable() && user?.roles?.includes('livechat-agent'))) {
+ return null;
+ }
+ const [status, setStatus] = useState(user?.statusLivechat === 'available');
+
+ useEffect(() => {
+ setStatus(user.statusLivechat === 'available');
+ }, [user.statusLivechat]);
+
+ const toggleLivechat = async() => {
+ try {
+ setStatus(v => !v);
+ await RocketChat.changeLivechatStatus();
+ } catch {
+ setStatus(v => !v);
+ }
+ };
+
+ return (
+
+
+
+ {I18n.t('Omnichannel')}
+ {inquiryEnabled
+ ? (
+
+ )
+ : null}
+
+
+
+ );
+});
+
+OmnichannelStatus.propTypes = {
+ searching: PropTypes.bool,
+ goQueue: PropTypes.func,
+ queueSize: PropTypes.number,
+ inquiryEnabled: PropTypes.bool,
+ theme: PropTypes.string,
+ user: PropTypes.shape({
+ roles: PropTypes.array,
+ statusLivechat: PropTypes.string
+ })
+};
+
+export default withTheme(OmnichannelStatus);
diff --git a/app/views/RoomsListView/ListHeader/Queue.js b/app/views/RoomsListView/ListHeader/Queue.js
deleted file mode 100644
index 0a85d657a..000000000
--- a/app/views/RoomsListView/ListHeader/Queue.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import React from 'react';
-import { View, Text, StyleSheet } from 'react-native';
-import PropTypes from 'prop-types';
-
-import Touch from '../../../utils/touch';
-import I18n from '../../../i18n';
-import styles from '../styles';
-import { themes } from '../../../constants/colors';
-import { withTheme } from '../../../theme';
-import UnreadBadge from '../../../presentation/UnreadBadge';
-
-const Queue = React.memo(({
- searching, goQueue, queueSize, inquiryEnabled, theme
-}) => {
- if (searching > 0 || !inquiryEnabled) {
- return null;
- }
- return (
-
-
- {I18n.t('Queued_chats')}
-
-
-
- );
-});
-
-Queue.propTypes = {
- searching: PropTypes.bool,
- goQueue: PropTypes.func,
- queueSize: PropTypes.number,
- inquiryEnabled: PropTypes.bool,
- theme: PropTypes.string
-};
-
-export default withTheme(Queue);
diff --git a/app/views/RoomsListView/ListHeader/Sort.js b/app/views/RoomsListView/ListHeader/Sort.js
index a2bdfac42..972b29801 100644
--- a/app/views/RoomsListView/ListHeader/Sort.js
+++ b/app/views/RoomsListView/ListHeader/Sort.js
@@ -28,8 +28,8 @@ const Sort = React.memo(({
{ borderBottomWidth: StyleSheet.hairlineWidth, borderColor: themes[theme].separatorColor }
]}
>
+
{I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') })}
-
);
diff --git a/app/views/RoomsListView/ListHeader/index.js b/app/views/RoomsListView/ListHeader/index.js
index 3de54b913..5fe4462fc 100644
--- a/app/views/RoomsListView/ListHeader/index.js
+++ b/app/views/RoomsListView/ListHeader/index.js
@@ -1,8 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
-import Queue from './Queue';
import Sort from './Sort';
+import OmnichannelStatus from './OmnichannelStatus';
const ListHeader = React.memo(({
searching,
@@ -10,11 +10,12 @@ const ListHeader = React.memo(({
toggleSort,
goQueue,
queueSize,
- inquiryEnabled
+ inquiryEnabled,
+ user
}) => (
<>
-
+
>
));
@@ -24,7 +25,8 @@ ListHeader.propTypes = {
toggleSort: PropTypes.func,
goQueue: PropTypes.func,
queueSize: PropTypes.number,
- inquiryEnabled: PropTypes.bool
+ inquiryEnabled: PropTypes.bool,
+ user: PropTypes.object
};
export default ListHeader;
diff --git a/app/views/RoomsListView/SortDropdown/index.js b/app/views/RoomsListView/SortDropdown/index.js
index f50c939d7..536f5738c 100644
--- a/app/views/RoomsListView/SortDropdown/index.js
+++ b/app/views/RoomsListView/SortDropdown/index.js
@@ -156,8 +156,8 @@ class Sort extends PureComponent {
>
+
{I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') })}
-
diff --git a/app/views/RoomsListView/index.js b/app/views/RoomsListView/index.js
index d9f37eba4..8f75eeb0b 100644
--- a/app/views/RoomsListView/index.js
+++ b/app/views/RoomsListView/index.js
@@ -62,7 +62,7 @@ import { goRoom } from '../../utils/goRoom';
import SafeAreaView from '../../containers/SafeAreaView';
import Header, { getHeaderTitlePosition } from '../../containers/Header';
import { withDimensions } from '../../dimensions';
-import { showErrorAlert } from '../../utils/info';
+import { showErrorAlert, showConfirmationAlert } from '../../utils/info';
import { getInquiryQueueSelector } from '../../selectors/inquiry';
const INITIAL_NUM_TO_RENDER = isTablet ? 20 : 12;
@@ -109,7 +109,8 @@ class RoomsListView extends React.Component {
user: PropTypes.shape({
id: PropTypes.string,
username: PropTypes.string,
- token: PropTypes.string
+ token: PropTypes.string,
+ statusLivechat: PropTypes.string
}),
server: PropTypes.string,
searchText: PropTypes.string,
@@ -450,7 +451,6 @@ class RoomsListView extends React.Component {
.observe();
}
-
this.querySubscription = observable.subscribe((data) => {
let tempChats = [];
let chats = data;
@@ -685,7 +685,28 @@ class RoomsListView extends React.Component {
goQueue = () => {
logEvent(events.RL_GO_QUEUE);
- const { navigation, isMasterDetail, queueSize } = this.props;
+ const {
+ navigation, isMasterDetail, queueSize, inquiryEnabled, user
+ } = this.props;
+
+ // if not-available, prompt to change to available
+ if (user?.statusLivechat !== 'available') {
+ showConfirmationAlert({
+ message: I18n.t('Omnichannel_enable_alert'),
+ callToAction: I18n.t('Yes'),
+ onPress: async() => {
+ try {
+ await RocketChat.changeLivechatStatus();
+ } catch {
+ // Do nothing
+ }
+ }
+ });
+ }
+
+ if (!inquiryEnabled) {
+ return;
+ }
// prevent navigation to empty list
if (!queueSize) {
return showErrorAlert(I18n.t('Queue_is_empty'), I18n.t('Oops'));
@@ -813,7 +834,9 @@ class RoomsListView extends React.Component {
renderListHeader = () => {
const { searching } = this.state;
- const { sortBy, queueSize, inquiryEnabled } = this.props;
+ const {
+ sortBy, queueSize, inquiryEnabled, user
+ } = this.props;
return (
);
};
diff --git a/app/views/RoomsListView/styles.js b/app/views/RoomsListView/styles.js
index f304518e1..6577cbbc9 100644
--- a/app/views/RoomsListView/styles.js
+++ b/app/views/RoomsListView/styles.js
@@ -23,7 +23,11 @@ export default StyleSheet.create({
sortToggleText: {
fontSize: 16,
flex: 1,
- marginLeft: 12,
+ ...sharedStyles.textRegular
+ },
+ queueToggleText: {
+ fontSize: 16,
+ flex: 1,
...sharedStyles.textRegular
},
dropdownContainer: {
@@ -58,6 +62,11 @@ export default StyleSheet.create({
height: 22,
marginHorizontal: 12
},
+ queueIcon: {
+ width: 22,
+ height: 22,
+ marginHorizontal: 12
+ },
groupTitleContainer: {
paddingHorizontal: 12,
paddingTop: 17,
@@ -116,5 +125,8 @@ export default StyleSheet.create({
serverSeparator: {
height: StyleSheet.hairlineWidth,
marginLeft: 72
+ },
+ omnichannelToggle: {
+ marginRight: 12
}
});
diff --git a/app/views/SettingsView/index.js b/app/views/SettingsView/index.js
index ee362b772..83e265461 100644
--- a/app/views/SettingsView/index.js
+++ b/app/views/SettingsView/index.js
@@ -36,7 +36,6 @@ import { LISTENER } from '../../containers/Toast';
import EventEmitter from '../../utils/events';
import { appStart as appStartAction, ROOT_LOADING } from '../../actions/app';
import { onReviewPress } from '../../utils/review';
-import { getUserSelector } from '../../selectors/login';
import SafeAreaView from '../../containers/SafeAreaView';
const SectionSeparator = React.memo(({ theme }) => (
@@ -73,20 +72,9 @@ class SettingsView extends React.Component {
isMasterDetail: PropTypes.bool,
logout: PropTypes.func.isRequired,
selectServerRequest: PropTypes.func,
- user: PropTypes.shape({
- roles: PropTypes.array,
- statusLivechat: PropTypes.string
- }),
appStart: PropTypes.func
}
- get showLivechat() {
- const { user } = this.props;
- const { roles } = user;
-
- return roles?.includes('livechat-agent');
- }
-
handleLogout = () => {
logEvent(events.SE_LOG_OUT);
showConfirmationAlert({
@@ -131,14 +119,6 @@ class SettingsView extends React.Component {
}
}
- toggleLivechat = async() => {
- try {
- await RocketChat.changeLivechatStatus();
- } catch {
- // Do nothing
- }
- }
-
navigateToScreen = (screen) => {
logEvent(events[`SE_GO_${ screen.replace('View', '').toUpperCase() }`]);
const { navigation } = this.props;
@@ -204,18 +184,6 @@ class SettingsView extends React.Component {
);
}
- renderLivechatSwitch = () => {
- const { user } = this.props;
- const { statusLivechat } = user;
- return (
-
- );
- }
-
render() {
const { server, isMasterDetail, theme } = this.props;
return (
@@ -336,18 +304,6 @@ class SettingsView extends React.Component {
- {this.showLivechat ? (
- <>
- this.renderLivechatSwitch()}
- theme={theme}
- />
-
- >
- ) : null}
-
({
server: state.server,
- user: getUserSelector(state),
allowCrashReport: state.crashReport.allowCrashReport,
isMasterDetail: state.app.isMasterDetail
});