feat(jaas) display messages about features that are disabled for jaas… (#9448)
* feat(jaas) display messages about features that are disabled for jaas users * code review
This commit is contained in:
parent
38b14c5d62
commit
ea56010e09
|
@ -104,5 +104,6 @@ $flagsImagePath: "../images/";
|
|||
@import 'connection-status';
|
||||
@import 'drawer';
|
||||
@import 'participants-pane';
|
||||
@import 'plan-limit';
|
||||
|
||||
/* Modules END */
|
||||
|
|
|
@ -323,6 +323,9 @@
|
|||
"userIdentifier": "User identifier",
|
||||
"userPassword": "User password",
|
||||
"videoLink": "Video link",
|
||||
"viewUpgradeOptions": "View upgrade options",
|
||||
"viewUpgradeOptionsContent": "To get unlimited access to premium features like recording, transcriptions, RTMP Streaming & more, you'll need to upgrade your plan.",
|
||||
"viewUpgradeOptionsTitle": "You discovered a premium feature!",
|
||||
"WaitForHostMsg": "The conference <b>{{room}}</b> has not yet started. If you are the host then please authenticate. Otherwise, please wait for the host to arrive.",
|
||||
"WaitForHostMsgWOk": "The conference <b>{{room}}</b> has not yet started. If you are the host then please press Ok to authenticate. Otherwise, please wait for the host to arrive.",
|
||||
"WaitingForHostTitle": "Waiting for the host ...",
|
||||
|
|
|
@ -30,6 +30,7 @@ import '../etherpad/middleware';
|
|||
import '../filmstrip/middleware';
|
||||
import '../follow-me/middleware';
|
||||
import '../invite/middleware';
|
||||
import '../jaas/middleware';
|
||||
import '../large-video/middleware';
|
||||
import '../lobby/middleware';
|
||||
import '../notifications/middleware';
|
||||
|
|
|
@ -36,6 +36,7 @@ import '../filmstrip/reducer';
|
|||
import '../follow-me/reducer';
|
||||
import '../google-api/reducer';
|
||||
import '../invite/reducer';
|
||||
import '../jaas/reducer';
|
||||
import '../large-video/reducer';
|
||||
import '../lobby/reducer';
|
||||
import '../notifications/reducer';
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Action used to store jaas customer details
|
||||
*/
|
||||
export const SET_DETAILS = 'SET_DETAILS';
|
|
@ -0,0 +1,72 @@
|
|||
// @flow
|
||||
|
||||
import { openDialog } from '../base/dialog';
|
||||
import { getVpaasTenant } from '../billing-counter/functions';
|
||||
|
||||
import { SET_DETAILS } from './actionTypes';
|
||||
import { PremiumFeatureDialog } from './components';
|
||||
import { isFeatureDisabled, sendGetDetailsRequest } from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Action used to set the jaas customer details in store.
|
||||
*
|
||||
* @param {Object} details - The customer details object.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function setCustomerDetails(details) {
|
||||
return {
|
||||
type: SET_DETAILS,
|
||||
payload: details
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request for retrieving jaas customer details.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function getCustomerDetails() {
|
||||
return async function(dispatch: Function, getState: Function) {
|
||||
const state = getState();
|
||||
const baseUrl = state['features/base/config'].jaasActuatorUrl;
|
||||
const jwt = state['features/base/jwt'].jwt;
|
||||
const appId = getVpaasTenant(state);
|
||||
|
||||
const shouldSendRequest = Boolean(baseUrl && jwt && appId);
|
||||
|
||||
if (shouldSendRequest) {
|
||||
try {
|
||||
const details = await sendGetDetailsRequest({
|
||||
baseUrl,
|
||||
jwt,
|
||||
appId
|
||||
});
|
||||
|
||||
dispatch(setCustomerDetails(details));
|
||||
} catch (err) {
|
||||
logger.error('Could not send request', err);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shows a dialog prompting users to upgrade, if requested feature is disabled.
|
||||
*
|
||||
* @param {string} feature - The feature to check availability for.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function maybeShowPremiumFeatureDialog(feature: string) {
|
||||
return function(dispatch: Function, getState: Function) {
|
||||
if (isFeatureDisabled(getState(), feature)) {
|
||||
dispatch(openDialog(PremiumFeatureDialog));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
// @flow
|
||||
|
||||
export * from './web';
|
|
@ -0,0 +1,62 @@
|
|||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
|
||||
import { Dialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { openURLInBrowser } from '../../../base/util';
|
||||
import { JAAS_UPGRADE_URL } from '../../constants';
|
||||
|
||||
/**
|
||||
* Component that renders the premium feature dialog.
|
||||
*
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
class PremiumFeatureDialog extends PureComponent<*> {
|
||||
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._onSubmitValue = this._onSubmitValue.bind(this);
|
||||
}
|
||||
|
||||
|
||||
_onSubmitValue: () => void;
|
||||
|
||||
/**
|
||||
* Callback to be invoked when the dialog ok is pressed.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_onSubmitValue() {
|
||||
openURLInBrowser(JAAS_UPGRADE_URL, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
hideCancelButton = { true }
|
||||
okKey = { t('dialog.viewUpgradeOptions') }
|
||||
onSubmit = { this._onSubmitValue }
|
||||
titleKey = { t('dialog.viewUpgradeOptionsTitle') }
|
||||
width = { 'small' }>
|
||||
<span>{t('dialog.viewUpgradeOptionsContent')}</span>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(PremiumFeatureDialog);
|
|
@ -0,0 +1,3 @@
|
|||
// @flow
|
||||
|
||||
export { default as PremiumFeatureDialog } from './PremiumFeatureDialog';
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* JaaS customer statuses which represent their account state
|
||||
*/
|
||||
export const STATUSES = {
|
||||
ACTIVE: 'ACTIVE',
|
||||
BLOCKED: 'BLOCKED'
|
||||
};
|
||||
|
||||
/**
|
||||
* Service features for JaaS users
|
||||
*/
|
||||
export const FEATURES = {
|
||||
INBOUND_CALL: 'inbound-call',
|
||||
OUTBOUND_CALL: 'outbound-call',
|
||||
RECORDING: 'recording',
|
||||
SIP_INBOUND_CALL: 'sip-inbound-call',
|
||||
SIP_OUTBOUND_CALL: 'sip-outbound-call',
|
||||
STREAMING: 'streaming',
|
||||
TRANSCRIPTION: 'transcription'
|
||||
};
|
||||
|
||||
/**
|
||||
* URL for displaying JaaS upgrade options
|
||||
*/
|
||||
export const JAAS_UPGRADE_URL = 'https://jaas.8x8.vc/#/plan/upgrade';
|
|
@ -0,0 +1,48 @@
|
|||
// @flow
|
||||
|
||||
/**
|
||||
* Sends a request for retrieving jaas customer details.
|
||||
*
|
||||
* @param {Object} reqData - The request info.
|
||||
* @param {string} reqData.appId - The client appId.
|
||||
* @param {string} reqData.baseUrl - The base url for the request.
|
||||
* @param {string} reqData.jwt - The JWT token.
|
||||
* @returns {void}
|
||||
*/
|
||||
export async function sendGetDetailsRequest({ appId, baseUrl, jwt }: {
|
||||
appId: string,
|
||||
baseUrl: string,
|
||||
jwt: string,
|
||||
}) {
|
||||
const fullUrl = `${baseUrl}/v1/customers/${encodeURIComponent(appId)}`;
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${jwt}`
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(fullUrl, {
|
||||
method: 'GET',
|
||||
headers
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
return res.json();
|
||||
}
|
||||
|
||||
throw new Error('Request not successful');
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the billing id for vpaas meetings.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @param {string} feature - Feature to be looked up for disable state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isFeatureDisabled(state: Object, feature: string) {
|
||||
return state['features/jaas'].disabledFeatures.includes(feature);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import { getLogger } from '../base/logging/functions';
|
||||
|
||||
export default getLogger('features/jaas');
|
|
@ -0,0 +1,33 @@
|
|||
import { redirectToStaticPage } from '../app/actions';
|
||||
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
import { SET_DETAILS } from './actionTypes';
|
||||
import { getCustomerDetails } from './actions';
|
||||
import { STATUSES } from './constants';
|
||||
|
||||
/**
|
||||
* The redux middleware for billing counter.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
|
||||
MiddlewareRegistry.register(store => next => async action => {
|
||||
switch (action.type) {
|
||||
case CONFERENCE_JOINED: {
|
||||
store.dispatch(getCustomerDetails());
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_DETAILS: {
|
||||
const { status } = action.payload;
|
||||
|
||||
if (status === STATUSES.BLOCKED) {
|
||||
store.dispatch(redirectToStaticPage('/static/planLimit.html'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
|
@ -0,0 +1,28 @@
|
|||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
SET_DETAILS
|
||||
} from './actionTypes';
|
||||
import { STATUSES } from './constants';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
disabledFeatures: [],
|
||||
status: STATUSES.ACTIVE
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen for actions that mutate the billing-counter state
|
||||
*/
|
||||
ReducerRegistry.register(
|
||||
'features/jaas', (state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
|
||||
case SET_DETAILS: {
|
||||
return action.payload;
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
},
|
||||
);
|
|
@ -8,6 +8,8 @@ import {
|
|||
isLocalParticipantModerator
|
||||
} from '../../../base/participants';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||
import { maybeShowPremiumFeatureDialog } from '../../../jaas/actions';
|
||||
import { FEATURES } from '../../../jaas/constants';
|
||||
import { getActiveSession } from '../../functions';
|
||||
|
||||
import {
|
||||
|
@ -73,12 +75,16 @@ export default class AbstractLiveStreamButton<P: Props> extends AbstractButton<P
|
|||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
async _handleClick() {
|
||||
const { _isLiveStreamRunning, dispatch } = this.props;
|
||||
|
||||
dispatch(openDialog(
|
||||
_isLiveStreamRunning ? StopLiveStreamDialog : StartLiveStreamDialog
|
||||
));
|
||||
const dialogShown = await dispatch(maybeShowPremiumFeatureDialog(FEATURES.RECORDING));
|
||||
|
||||
if (!dialogShown) {
|
||||
dispatch(openDialog(
|
||||
_isLiveStreamRunning ? StopLiveStreamDialog : StartLiveStreamDialog
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,8 @@ import {
|
|||
isLocalParticipantModerator
|
||||
} from '../../../base/participants';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||
import { maybeShowPremiumFeatureDialog } from '../../../jaas/actions';
|
||||
import { FEATURES } from '../../../jaas/constants';
|
||||
import { getActiveSession } from '../../functions';
|
||||
|
||||
import { StartRecordingDialog, StopRecordingDialog } from './_';
|
||||
|
@ -74,7 +76,7 @@ export default class AbstractRecordButton<P: Props> extends AbstractButton<P, *>
|
|||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
async _handleClick() {
|
||||
const { _isRecordingRunning, dispatch } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent(
|
||||
|
@ -84,9 +86,13 @@ export default class AbstractRecordButton<P: Props> extends AbstractButton<P, *>
|
|||
type: JitsiRecordingConstants.mode.FILE
|
||||
}));
|
||||
|
||||
dispatch(openDialog(
|
||||
_isRecordingRunning ? StopRecordingDialog : StartRecordingDialog
|
||||
));
|
||||
const dialogShown = await dispatch(maybeShowPremiumFeatureDialog(FEATURES.RECORDING));
|
||||
|
||||
if (!dialogShown) {
|
||||
dispatch(openDialog(
|
||||
_isRecordingRunning ? StopRecordingDialog : StartRecordingDialog
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
import { createToolbarEvent, sendAnalytics } from '../../analytics';
|
||||
import { isLocalParticipantModerator } from '../../base/participants';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
|
||||
import { maybeShowPremiumFeatureDialog } from '../../jaas/actions';
|
||||
import { FEATURES } from '../../jaas/constants';
|
||||
import { toggleRequestingSubtitles } from '../actions';
|
||||
|
||||
export type AbstractProps = AbstractButtonProps & {
|
||||
|
@ -35,7 +37,7 @@ export class AbstractClosedCaptionButton
|
|||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
async _handleClick() {
|
||||
const { _requestingSubtitles, dispatch } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('transcribing.ccButton',
|
||||
|
@ -43,7 +45,12 @@ export class AbstractClosedCaptionButton
|
|||
'requesting_subtitles': Boolean(_requestingSubtitles)
|
||||
}));
|
||||
|
||||
dispatch(toggleRequestingSubtitles());
|
||||
|
||||
const dialogShown = await dispatch(maybeShowPremiumFeatureDialog(FEATURES.RECORDING));
|
||||
|
||||
if (!dialogShown) {
|
||||
dispatch(toggleRequestingSubtitles());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue