[IMPROVEMENT] Verify Enterprise status on Omnichannel (#2399)
* Add enterpriseModules on Redux * Fetch enterprise modules and put on redux * hasLicense * Clear modules * Hide omnichannel rooms * Minor refactor * Hide omnichannel toggle * Check license on user status * Apply on search * lint * Look for 'livechat-enterprise' * One module is enough to enable the features * Unhide omnichannel rooms * Sort tweaks * Move omnichannel toggle to RoomsListView * Remove omnichannel toggle from SettingsView * Fix toggle * Ask to enable omnichannel * Lint * Fix issues found on review
This commit is contained in:
parent
54c4614e2e
commit
b06bf7fcb5
|
@ -66,3 +66,4 @@ export const INVITE_LINKS = createRequestTypes('INVITE_LINKS', [
|
||||||
]);
|
]);
|
||||||
export const SETTINGS = createRequestTypes('SETTINGS', ['CLEAR', 'ADD']);
|
export const SETTINGS = createRequestTypes('SETTINGS', ['CLEAR', 'ADD']);
|
||||||
export const APP_STATE = createRequestTypes('APP_STATE', ['FOREGROUND', 'BACKGROUND']);
|
export const APP_STATE = createRequestTypes('APP_STATE', ['FOREGROUND', 'BACKGROUND']);
|
||||||
|
export const ENTERPRISE_MODULES = createRequestTypes('ENTERPRISE_MODULES', ['CLEAR', 'SET']);
|
||||||
|
|
|
@ -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
|
||||||
|
};
|
||||||
|
}
|
|
@ -339,6 +339,7 @@ export default {
|
||||||
Offline: 'Offline',
|
Offline: 'Offline',
|
||||||
Oops: 'Oops!',
|
Oops: 'Oops!',
|
||||||
Omnichannel: 'Omnichannel',
|
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_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_join_workspace: 'Join a workspace',
|
||||||
Onboarding_subtitle: 'Beyond Team Collaboration',
|
Onboarding_subtitle: 'Beyond Team Collaboration',
|
||||||
|
|
|
@ -314,6 +314,8 @@ export default {
|
||||||
Not_RC_Server: 'Este não é um servidor Rocket.Chat.\n{{contact}}',
|
Not_RC_Server: 'Este não é um servidor Rocket.Chat.\n{{contact}}',
|
||||||
No_available_agents_to_transfer: 'Nenhum agente disponível para transferência',
|
No_available_agents_to_transfer: 'Nenhum agente disponível para transferência',
|
||||||
Offline: 'Offline',
|
Offline: 'Offline',
|
||||||
|
Omnichannel: 'Omnichannel',
|
||||||
|
Omnichannel_enable_alert: 'Você não está disponível no Omnichannel. Você quer ficar disponível?',
|
||||||
Oops: 'Ops!',
|
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_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',
|
Onboarding_join_workspace: 'Entre numa workspace',
|
||||||
|
|
|
@ -27,4 +27,6 @@ export default class Server extends Model {
|
||||||
@field('biometry') biometry;
|
@field('biometry') biometry;
|
||||||
|
|
||||||
@field('unique_id') uniqueID;
|
@field('unique_id') uniqueID;
|
||||||
|
|
||||||
|
@field('enterprise_modules') enterpriseModules;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,17 @@ export default schemaMigrations({
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toVersion: 6,
|
||||||
|
steps: [
|
||||||
|
addColumns({
|
||||||
|
table: 'servers',
|
||||||
|
columns: [
|
||||||
|
{ name: 'enterprise_modules', type: 'string', isOptional: true }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
export default appSchema({
|
export default appSchema({
|
||||||
version: 5,
|
version: 6,
|
||||||
tables: [
|
tables: [
|
||||||
tableSchema({
|
tableSchema({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
|
@ -29,7 +29,8 @@ export default appSchema({
|
||||||
{ name: 'auto_lock', type: 'boolean', isOptional: true },
|
{ name: 'auto_lock', type: 'boolean', isOptional: true },
|
||||||
{ name: 'auto_lock_time', type: 'number', isOptional: true },
|
{ name: 'auto_lock_time', type: 'number', isOptional: true },
|
||||||
{ name: 'biometry', type: 'boolean', 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 }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
|
@ -244,7 +244,9 @@ export default function subscribeRooms() {
|
||||||
const [, ev] = ddpMessage.fields.eventName.split('/');
|
const [, ev] = ddpMessage.fields.eventName.split('/');
|
||||||
if (/userData/.test(ev)) {
|
if (/userData/.test(ev)) {
|
||||||
const [{ diff }] = ddpMessage.fields.args;
|
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 (/subscriptions/.test(ev)) {
|
||||||
if (type === 'removed') {
|
if (type === 'removed') {
|
||||||
|
|
|
@ -29,6 +29,9 @@ import getSettings, { getLoginSettings, setSettings } from './methods/getSetting
|
||||||
import getRooms from './methods/getRooms';
|
import getRooms from './methods/getRooms';
|
||||||
import getPermissions from './methods/getPermissions';
|
import getPermissions from './methods/getPermissions';
|
||||||
import { getCustomEmojis, setCustomEmojis } from './methods/getCustomEmojis';
|
import { getCustomEmojis, setCustomEmojis } from './methods/getCustomEmojis';
|
||||||
|
import {
|
||||||
|
getEnterpriseModules, setEnterpriseModules, hasLicense, isOmnichannelModuleAvailable
|
||||||
|
} from './methods/enterpriseModules';
|
||||||
import getSlashCommands from './methods/getSlashCommands';
|
import getSlashCommands from './methods/getSlashCommands';
|
||||||
import getRoles from './methods/getRoles';
|
import getRoles from './methods/getRoles';
|
||||||
import canOpenRoom from './methods/canOpenRoom';
|
import canOpenRoom from './methods/canOpenRoom';
|
||||||
|
@ -519,6 +522,7 @@ const RocketChat = {
|
||||||
} else if (!filterUsers && filterRooms) {
|
} else if (!filterUsers && filterRooms) {
|
||||||
data = data.filter(item => item.t !== 'd' || RocketChat.isGroupChat(item));
|
data = data.filter(item => item.t !== 'd' || RocketChat.isGroupChat(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
data = data.slice(0, 7);
|
data = data.slice(0, 7);
|
||||||
|
|
||||||
data = data.map((sub) => {
|
data = data.map((sub) => {
|
||||||
|
@ -620,6 +624,10 @@ const RocketChat = {
|
||||||
getPermissions,
|
getPermissions,
|
||||||
getCustomEmojis,
|
getCustomEmojis,
|
||||||
setCustomEmojis,
|
setCustomEmojis,
|
||||||
|
getEnterpriseModules,
|
||||||
|
setEnterpriseModules,
|
||||||
|
hasLicense,
|
||||||
|
isOmnichannelModuleAvailable,
|
||||||
getSlashCommands,
|
getSlashCommands,
|
||||||
getRoles,
|
getRoles,
|
||||||
parseSettings: settings => settings.reduce((ret, item) => {
|
parseSettings: settings => settings.reduce((ret, item) => {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
|
@ -17,6 +17,7 @@ import usersTyping from './usersTyping';
|
||||||
import inviteLinks from './inviteLinks';
|
import inviteLinks from './inviteLinks';
|
||||||
import createDiscussion from './createDiscussion';
|
import createDiscussion from './createDiscussion';
|
||||||
import inquiry from './inquiry';
|
import inquiry from './inquiry';
|
||||||
|
import enterpriseModules from './enterpriseModules';
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
settings,
|
settings,
|
||||||
|
@ -36,5 +37,6 @@ export default combineReducers({
|
||||||
usersTyping,
|
usersTyping,
|
||||||
inviteLinks,
|
inviteLinks,
|
||||||
createDiscussion,
|
createDiscussion,
|
||||||
inquiry
|
inquiry,
|
||||||
|
enterpriseModules
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {
|
||||||
loginFailure, loginSuccess, setUser, logout
|
loginFailure, loginSuccess, setUser, logout
|
||||||
} from '../actions/login';
|
} from '../actions/login';
|
||||||
import { roomsRequest } from '../actions/rooms';
|
import { roomsRequest } from '../actions/rooms';
|
||||||
import { inquiryRequest } from '../actions/inquiry';
|
import { inquiryRequest, inquiryReset } from '../actions/inquiry';
|
||||||
import { toMomentLocale } from '../utils/moment';
|
import { toMomentLocale } from '../utils/moment';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import log, { logEvent, events } from '../utils/log';
|
import log, { logEvent, events } from '../utils/log';
|
||||||
|
@ -85,6 +85,14 @@ const fetchUsersPresence = function* fetchUserPresence() {
|
||||||
RocketChat.subscribeUsersPresence();
|
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 }) {
|
const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
||||||
try {
|
try {
|
||||||
const adding = yield select(state => state.server.adding);
|
const adding = yield select(state => state.server.adding);
|
||||||
|
@ -94,13 +102,13 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
||||||
|
|
||||||
const server = yield select(getServer);
|
const server = yield select(getServer);
|
||||||
yield put(roomsRequest());
|
yield put(roomsRequest());
|
||||||
yield put(inquiryRequest());
|
|
||||||
yield fork(fetchPermissions);
|
yield fork(fetchPermissions);
|
||||||
yield fork(fetchCustomEmojis);
|
yield fork(fetchCustomEmojis);
|
||||||
yield fork(fetchRoles);
|
yield fork(fetchRoles);
|
||||||
yield fork(fetchSlashCommands);
|
yield fork(fetchSlashCommands);
|
||||||
yield fork(registerPushToken);
|
yield fork(registerPushToken);
|
||||||
yield fork(fetchUsersPresence);
|
yield fork(fetchUsersPresence);
|
||||||
|
yield fork(fetchEnterpriseModules, { user });
|
||||||
|
|
||||||
I18n.locale = user.language;
|
I18n.locale = user.language;
|
||||||
moment.locale(toMomentLocale(user.language));
|
moment.locale(toMomentLocale(user.language));
|
||||||
|
@ -210,8 +218,12 @@ const handleSetUser = function* handleSetUser({ user }) {
|
||||||
yield put(setActiveUsers({ [userId]: user }));
|
yield put(setActiveUsers({ [userId]: user }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user && user.statusLivechat) {
|
if (user?.statusLivechat && RocketChat.isOmnichannelModuleAvailable()) {
|
||||||
yield put(inquiryRequest());
|
if (user.statusLivechat === 'available') {
|
||||||
|
yield put(inquiryRequest());
|
||||||
|
} else {
|
||||||
|
yield put(inquiryReset());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -109,6 +109,7 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
|
||||||
// and block the selectServerSuccess raising multiples errors
|
// and block the selectServerSuccess raising multiples errors
|
||||||
RocketChat.setSettings();
|
RocketChat.setSettings();
|
||||||
RocketChat.setCustomEmojis();
|
RocketChat.setCustomEmojis();
|
||||||
|
RocketChat.setEnterpriseModules();
|
||||||
|
|
||||||
let serverInfo;
|
let serverInfo;
|
||||||
if (fetchVersion) {
|
if (fetchVersion) {
|
||||||
|
|
|
@ -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 (
|
||||||
|
<Touch
|
||||||
|
onPress={goQueue}
|
||||||
|
theme={theme}
|
||||||
|
style={{ backgroundColor: themes[theme].headerSecondaryBackground }}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.dropdownContainerHeader,
|
||||||
|
{ borderBottomWidth: StyleSheet.hairlineWidth, borderColor: themes[theme].separatorColor }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<CustomIcon style={[styles.queueIcon, { color: themes[theme].auxiliaryText }]} size={22} name='omnichannel' />
|
||||||
|
<Text style={[styles.queueToggleText, { color: themes[theme].auxiliaryText }]}>{I18n.t('Omnichannel')}</Text>
|
||||||
|
{inquiryEnabled
|
||||||
|
? (
|
||||||
|
<UnreadBadge
|
||||||
|
style={styles.queueIcon}
|
||||||
|
unread={queueSize}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
<Switch
|
||||||
|
style={styles.omnichannelToggle}
|
||||||
|
value={status}
|
||||||
|
trackColor={SWITCH_TRACK_COLOR}
|
||||||
|
onValueChange={toggleLivechat}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</Touch>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
|
@ -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 (
|
|
||||||
<Touch
|
|
||||||
onPress={goQueue}
|
|
||||||
theme={theme}
|
|
||||||
style={{ backgroundColor: themes[theme].headerSecondaryBackground }}
|
|
||||||
>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
styles.dropdownContainerHeader,
|
|
||||||
{ borderBottomWidth: StyleSheet.hairlineWidth, borderColor: themes[theme].separatorColor }
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text style={[styles.sortToggleText, { color: themes[theme].auxiliaryText }]}>{I18n.t('Queued_chats')}</Text>
|
|
||||||
<UnreadBadge
|
|
||||||
style={styles.sortIcon}
|
|
||||||
unread={queueSize}
|
|
||||||
theme={theme}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</Touch>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
Queue.propTypes = {
|
|
||||||
searching: PropTypes.bool,
|
|
||||||
goQueue: PropTypes.func,
|
|
||||||
queueSize: PropTypes.number,
|
|
||||||
inquiryEnabled: PropTypes.bool,
|
|
||||||
theme: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withTheme(Queue);
|
|
|
@ -28,8 +28,8 @@ const Sort = React.memo(({
|
||||||
{ borderBottomWidth: StyleSheet.hairlineWidth, borderColor: themes[theme].separatorColor }
|
{ borderBottomWidth: StyleSheet.hairlineWidth, borderColor: themes[theme].separatorColor }
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
<CustomIcon style={[styles.sortIcon, { color: themes[theme].auxiliaryText }]} size={22} name='sort' />
|
||||||
<Text style={[styles.sortToggleText, { color: themes[theme].auxiliaryText }]}>{I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') })}</Text>
|
<Text style={[styles.sortToggleText, { color: themes[theme].auxiliaryText }]}>{I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') })}</Text>
|
||||||
<CustomIcon style={[styles.sortIcon, { color: themes[theme].auxiliaryText }]} size={22} name='sort-az' />
|
|
||||||
</View>
|
</View>
|
||||||
</Touch>
|
</Touch>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import Queue from './Queue';
|
|
||||||
import Sort from './Sort';
|
import Sort from './Sort';
|
||||||
|
import OmnichannelStatus from './OmnichannelStatus';
|
||||||
|
|
||||||
const ListHeader = React.memo(({
|
const ListHeader = React.memo(({
|
||||||
searching,
|
searching,
|
||||||
|
@ -10,11 +10,12 @@ const ListHeader = React.memo(({
|
||||||
toggleSort,
|
toggleSort,
|
||||||
goQueue,
|
goQueue,
|
||||||
queueSize,
|
queueSize,
|
||||||
inquiryEnabled
|
inquiryEnabled,
|
||||||
|
user
|
||||||
}) => (
|
}) => (
|
||||||
<>
|
<>
|
||||||
<Sort searching={searching} sortBy={sortBy} toggleSort={toggleSort} />
|
<Sort searching={searching} sortBy={sortBy} toggleSort={toggleSort} />
|
||||||
<Queue searching={searching} goQueue={goQueue} queueSize={queueSize} inquiryEnabled={inquiryEnabled} />
|
<OmnichannelStatus searching={searching} goQueue={goQueue} inquiryEnabled={inquiryEnabled} queueSize={queueSize} user={user} />
|
||||||
</>
|
</>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -24,7 +25,8 @@ ListHeader.propTypes = {
|
||||||
toggleSort: PropTypes.func,
|
toggleSort: PropTypes.func,
|
||||||
goQueue: PropTypes.func,
|
goQueue: PropTypes.func,
|
||||||
queueSize: PropTypes.number,
|
queueSize: PropTypes.number,
|
||||||
inquiryEnabled: PropTypes.bool
|
inquiryEnabled: PropTypes.bool,
|
||||||
|
user: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ListHeader;
|
export default ListHeader;
|
||||||
|
|
|
@ -156,8 +156,8 @@ class Sort extends PureComponent {
|
||||||
>
|
>
|
||||||
<View style={[styles.dropdownContainerHeader, { borderColor: themes[theme].separatorColor }]}>
|
<View style={[styles.dropdownContainerHeader, { borderColor: themes[theme].separatorColor }]}>
|
||||||
<View style={styles.sortItemContainer}>
|
<View style={styles.sortItemContainer}>
|
||||||
|
<CustomIcon style={[styles.sortIcon, { color: themes[theme].auxiliaryText }]} size={22} name='sort' />
|
||||||
<Text style={[styles.sortToggleText, { color: themes[theme].auxiliaryText }]}>{I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') })}</Text>
|
<Text style={[styles.sortToggleText, { color: themes[theme].auxiliaryText }]}>{I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') })}</Text>
|
||||||
<CustomIcon style={[styles.sortIcon, { color: themes[theme].auxiliaryText }]} size={22} name='sort-az' />
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Touch>
|
</Touch>
|
||||||
|
|
|
@ -62,7 +62,7 @@ import { goRoom } from '../../utils/goRoom';
|
||||||
import SafeAreaView from '../../containers/SafeAreaView';
|
import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
import Header, { getHeaderTitlePosition } from '../../containers/Header';
|
import Header, { getHeaderTitlePosition } from '../../containers/Header';
|
||||||
import { withDimensions } from '../../dimensions';
|
import { withDimensions } from '../../dimensions';
|
||||||
import { showErrorAlert } from '../../utils/info';
|
import { showErrorAlert, showConfirmationAlert } from '../../utils/info';
|
||||||
import { getInquiryQueueSelector } from '../../selectors/inquiry';
|
import { getInquiryQueueSelector } from '../../selectors/inquiry';
|
||||||
|
|
||||||
const INITIAL_NUM_TO_RENDER = isTablet ? 20 : 12;
|
const INITIAL_NUM_TO_RENDER = isTablet ? 20 : 12;
|
||||||
|
@ -109,7 +109,8 @@ class RoomsListView extends React.Component {
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
username: PropTypes.string,
|
username: PropTypes.string,
|
||||||
token: PropTypes.string
|
token: PropTypes.string,
|
||||||
|
statusLivechat: PropTypes.string
|
||||||
}),
|
}),
|
||||||
server: PropTypes.string,
|
server: PropTypes.string,
|
||||||
searchText: PropTypes.string,
|
searchText: PropTypes.string,
|
||||||
|
@ -450,7 +451,6 @@ class RoomsListView extends React.Component {
|
||||||
.observe();
|
.observe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.querySubscription = observable.subscribe((data) => {
|
this.querySubscription = observable.subscribe((data) => {
|
||||||
let tempChats = [];
|
let tempChats = [];
|
||||||
let chats = data;
|
let chats = data;
|
||||||
|
@ -685,7 +685,28 @@ class RoomsListView extends React.Component {
|
||||||
|
|
||||||
goQueue = () => {
|
goQueue = () => {
|
||||||
logEvent(events.RL_GO_QUEUE);
|
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
|
// prevent navigation to empty list
|
||||||
if (!queueSize) {
|
if (!queueSize) {
|
||||||
return showErrorAlert(I18n.t('Queue_is_empty'), I18n.t('Oops'));
|
return showErrorAlert(I18n.t('Queue_is_empty'), I18n.t('Oops'));
|
||||||
|
@ -813,7 +834,9 @@ class RoomsListView extends React.Component {
|
||||||
|
|
||||||
renderListHeader = () => {
|
renderListHeader = () => {
|
||||||
const { searching } = this.state;
|
const { searching } = this.state;
|
||||||
const { sortBy, queueSize, inquiryEnabled } = this.props;
|
const {
|
||||||
|
sortBy, queueSize, inquiryEnabled, user
|
||||||
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<ListHeader
|
<ListHeader
|
||||||
searching={searching}
|
searching={searching}
|
||||||
|
@ -823,6 +846,7 @@ class RoomsListView extends React.Component {
|
||||||
goQueue={this.goQueue}
|
goQueue={this.goQueue}
|
||||||
queueSize={queueSize}
|
queueSize={queueSize}
|
||||||
inquiryEnabled={inquiryEnabled}
|
inquiryEnabled={inquiryEnabled}
|
||||||
|
user={user}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,7 +23,11 @@ export default StyleSheet.create({
|
||||||
sortToggleText: {
|
sortToggleText: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
marginLeft: 12,
|
...sharedStyles.textRegular
|
||||||
|
},
|
||||||
|
queueToggleText: {
|
||||||
|
fontSize: 16,
|
||||||
|
flex: 1,
|
||||||
...sharedStyles.textRegular
|
...sharedStyles.textRegular
|
||||||
},
|
},
|
||||||
dropdownContainer: {
|
dropdownContainer: {
|
||||||
|
@ -58,6 +62,11 @@ export default StyleSheet.create({
|
||||||
height: 22,
|
height: 22,
|
||||||
marginHorizontal: 12
|
marginHorizontal: 12
|
||||||
},
|
},
|
||||||
|
queueIcon: {
|
||||||
|
width: 22,
|
||||||
|
height: 22,
|
||||||
|
marginHorizontal: 12
|
||||||
|
},
|
||||||
groupTitleContainer: {
|
groupTitleContainer: {
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
paddingTop: 17,
|
paddingTop: 17,
|
||||||
|
@ -116,5 +125,8 @@ export default StyleSheet.create({
|
||||||
serverSeparator: {
|
serverSeparator: {
|
||||||
height: StyleSheet.hairlineWidth,
|
height: StyleSheet.hairlineWidth,
|
||||||
marginLeft: 72
|
marginLeft: 72
|
||||||
|
},
|
||||||
|
omnichannelToggle: {
|
||||||
|
marginRight: 12
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -36,7 +36,6 @@ import { LISTENER } from '../../containers/Toast';
|
||||||
import EventEmitter from '../../utils/events';
|
import EventEmitter from '../../utils/events';
|
||||||
import { appStart as appStartAction, ROOT_LOADING } from '../../actions/app';
|
import { appStart as appStartAction, ROOT_LOADING } from '../../actions/app';
|
||||||
import { onReviewPress } from '../../utils/review';
|
import { onReviewPress } from '../../utils/review';
|
||||||
import { getUserSelector } from '../../selectors/login';
|
|
||||||
import SafeAreaView from '../../containers/SafeAreaView';
|
import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
|
|
||||||
const SectionSeparator = React.memo(({ theme }) => (
|
const SectionSeparator = React.memo(({ theme }) => (
|
||||||
|
@ -73,20 +72,9 @@ class SettingsView extends React.Component {
|
||||||
isMasterDetail: PropTypes.bool,
|
isMasterDetail: PropTypes.bool,
|
||||||
logout: PropTypes.func.isRequired,
|
logout: PropTypes.func.isRequired,
|
||||||
selectServerRequest: PropTypes.func,
|
selectServerRequest: PropTypes.func,
|
||||||
user: PropTypes.shape({
|
|
||||||
roles: PropTypes.array,
|
|
||||||
statusLivechat: PropTypes.string
|
|
||||||
}),
|
|
||||||
appStart: PropTypes.func
|
appStart: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
get showLivechat() {
|
|
||||||
const { user } = this.props;
|
|
||||||
const { roles } = user;
|
|
||||||
|
|
||||||
return roles?.includes('livechat-agent');
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLogout = () => {
|
handleLogout = () => {
|
||||||
logEvent(events.SE_LOG_OUT);
|
logEvent(events.SE_LOG_OUT);
|
||||||
showConfirmationAlert({
|
showConfirmationAlert({
|
||||||
|
@ -131,14 +119,6 @@ class SettingsView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleLivechat = async() => {
|
|
||||||
try {
|
|
||||||
await RocketChat.changeLivechatStatus();
|
|
||||||
} catch {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
navigateToScreen = (screen) => {
|
navigateToScreen = (screen) => {
|
||||||
logEvent(events[`SE_GO_${ screen.replace('View', '').toUpperCase() }`]);
|
logEvent(events[`SE_GO_${ screen.replace('View', '').toUpperCase() }`]);
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
|
@ -204,18 +184,6 @@ class SettingsView extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLivechatSwitch = () => {
|
|
||||||
const { user } = this.props;
|
|
||||||
const { statusLivechat } = user;
|
|
||||||
return (
|
|
||||||
<Switch
|
|
||||||
value={statusLivechat === 'available'}
|
|
||||||
trackColor={SWITCH_TRACK_COLOR}
|
|
||||||
onValueChange={this.toggleLivechat}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { server, isMasterDetail, theme } = this.props;
|
const { server, isMasterDetail, theme } = this.props;
|
||||||
return (
|
return (
|
||||||
|
@ -336,18 +304,6 @@ class SettingsView extends React.Component {
|
||||||
|
|
||||||
<SectionSeparator theme={theme} />
|
<SectionSeparator theme={theme} />
|
||||||
|
|
||||||
{this.showLivechat ? (
|
|
||||||
<>
|
|
||||||
<ListItem
|
|
||||||
title={I18n.t('Omnichannel')}
|
|
||||||
testID='settings-view-livechat'
|
|
||||||
right={() => this.renderLivechatSwitch()}
|
|
||||||
theme={theme}
|
|
||||||
/>
|
|
||||||
<SectionSeparator theme={theme} />
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<ListItem
|
<ListItem
|
||||||
title={I18n.t('Send_crash_report')}
|
title={I18n.t('Send_crash_report')}
|
||||||
testID='settings-view-crash-report'
|
testID='settings-view-crash-report'
|
||||||
|
@ -387,7 +343,6 @@ class SettingsView extends React.Component {
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
server: state.server,
|
server: state.server,
|
||||||
user: getUserSelector(state),
|
|
||||||
allowCrashReport: state.crashReport.allowCrashReport,
|
allowCrashReport: state.crashReport.allowCrashReport,
|
||||||
isMasterDetail: state.app.isMasterDetail
|
isMasterDetail: state.app.isMasterDetail
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue