+ );
+ }
+
+ /**
+ * Creates a ReactElement for displaying the current password.
+ *
+ * @private
+ * @returns {ReactElement}
+ */
+ _getPasswordPreviewText() {
+ return (
+
+
+ { `${this.props.t('dialog.currentPassword')} ` }
+
+
+ { this.props.lockedLocally
+ ? this.props.password
+ : this.props.t('passwordSetRemotely') }
+
+
+ );
+ }
+
+ /**
+ * Dispatches a request to remove any set password on the JitsiConference.
+ *
+ * @private
+ * @returns {void}
+ */
+ _onClick() {
+ const conference = this.props.conference;
+
+ this.props.dispatch(setPassword(
+ conference,
+ conference.lock,
+ ''
+ ));
+ }
+}
+
+export default translate(connect()(RemovePasswordForm));
diff --git a/react/features/invite/components/ShareLinkForm.js b/react/features/invite/components/ShareLinkForm.js
new file mode 100644
index 000000000..f2c52deeb
--- /dev/null
+++ b/react/features/invite/components/ShareLinkForm.js
@@ -0,0 +1,110 @@
+import React, { Component } from 'react';
+
+import { translate } from '../../base/i18n';
+
+const logger = require('jitsi-meet-logger').getLogger(__filename);
+
+/**
+ * A React Component for displaying a value with a copy button that can be
+ * clicked to copy the value onto the clipboard.
+ */
+class ShareLinkForm extends Component {
+ /**
+ * ShareLinkForm component's property types.
+ *
+ * @static
+ */
+ static propTypes = {
+ /**
+ * Invoked to obtain translated strings.
+ */
+ t: React.PropTypes.func,
+
+ /**
+ * The value to be displayed and copied onto the clipboard.
+ */
+ toCopy: React.PropTypes.string
+ }
+
+ /**
+ * Initializes a new ShareLinkForm instance.
+ *
+ * @param {Object} props - The read-only properties with which the new
+ * instance is to be initialized.
+ */
+ constructor(props) {
+ super(props);
+
+ this._inputElement = null;
+
+ this._onClick = this._onClick.bind(this);
+ this._setInput = this._setInput.bind(this);
+ }
+
+ /**
+ * Implements React's {@link Component#render()}.
+ *
+ * @inheritdoc
+ * @returns {ReactElement}
+ */
+ render() {
+ const inputValue = this.props.toCopy
+ || this.props.t('inviteUrlDefaultMsg');
+
+ // FIXME input is used here instead of atlaskit field-text because
+ // field-text does not currently support readonly
+ return (
+
+
+
+
+
+
+
+ );
+ }
+
+ /**
+ * Copies the passed in value to the clipboard.
+ *
+ * @private
+ * @returns {void}
+ */
+ _onClick() {
+ try {
+ this._inputElement.select();
+ document.execCommand('copy');
+ this._inputElement.blur();
+ } catch (err) {
+ logger.error('error when copying the text', err);
+ }
+ }
+
+ /**
+ * Sets the internal reference to the DOM element for the input field so it
+ * may be accessed directly.
+ *
+ * @param {Object} element - DOM element for the component's input.
+ * @private
+ * @returns {void}
+ */
+ _setInput(element) {
+ this._inputElement = element;
+ }
+}
+
+export default translate(ShareLinkForm);
diff --git a/react/features/invite/components/index.js b/react/features/invite/components/index.js
new file mode 100644
index 000000000..36cdc6dc8
--- /dev/null
+++ b/react/features/invite/components/index.js
@@ -0,0 +1 @@
+export { default as InviteDialog } from './InviteDialog';
diff --git a/react/features/invite/index.js b/react/features/invite/index.js
new file mode 100644
index 000000000..3c46ed49d
--- /dev/null
+++ b/react/features/invite/index.js
@@ -0,0 +1,2 @@
+export * from './actions';
+export * from './components';
diff --git a/react/features/room-lock/components/PasswordRequiredPrompt.web.js b/react/features/room-lock/components/PasswordRequiredPrompt.web.js
index 0d42014f9..9ead15c95 100644
--- a/react/features/room-lock/components/PasswordRequiredPrompt.web.js
+++ b/react/features/room-lock/components/PasswordRequiredPrompt.web.js
@@ -3,6 +3,8 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import AKFieldText from '@atlaskit/field-text';
+import UIEvents from '../../../../service/UI/UIEvents';
+
import { setPassword } from '../../base/conference';
import { Dialog } from '../../base/dialog';
import { translate } from '../../base/i18n';
@@ -109,9 +111,9 @@ class PasswordRequiredPrompt extends Component {
// password required will be received and the room again
// will be marked as locked.
if (!this.state.password || this.state.password === '') {
- // XXX temporary solution till we move the whole invite logic
- // in react
- APP.conference.invite.setLockedFromElsewhere(false);
+ // XXX temporary solution while some components are not listening
+ // for lock state updates in redux
+ APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK, false);
}
this.props.dispatch(setPassword(
diff --git a/react/features/room-lock/constants.js b/react/features/room-lock/constants.js
new file mode 100644
index 000000000..0ca678407
--- /dev/null
+++ b/react/features/room-lock/constants.js
@@ -0,0 +1,12 @@
+/**
+ * The room lock state where the password was set by the current user.
+ *
+ * @type {string}
+ */
+export const LOCKED_LOCALLY = 'LOCKED_LOCALLY';
+
+/**
+ * The room lock state where the password was set by a remote user.
+ * @type {string}
+ */
+export const LOCKED_REMOTELY = 'LOCKED_REMOTELY';
diff --git a/react/features/room-lock/index.js b/react/features/room-lock/index.js
index a640fd3bf..d7ebc6536 100644
--- a/react/features/room-lock/index.js
+++ b/react/features/room-lock/index.js
@@ -1,4 +1,5 @@
export * from './actions';
export * from './components';
+export * from './constants';
import './middleware';
diff --git a/react/features/room-lock/middleware.js b/react/features/room-lock/middleware.js
index 10a8fb6e5..7b392b994 100644
--- a/react/features/room-lock/middleware.js
+++ b/react/features/room-lock/middleware.js
@@ -1,8 +1,16 @@
/* global APP */
-import JitsiMeetJS from '../base/lib-jitsi-meet';
+const logger = require('jitsi-meet-logger').getLogger(__filename);
-import { CONFERENCE_FAILED } from '../base/conference';
+import UIEvents from '../../../service/UI/UIEvents';
+
+import {
+ CONFERENCE_FAILED,
+ LOCK_STATE_CHANGED,
+ SET_PASSWORD_FAILED
+} from '../base/conference';
+import JitsiMeetJS from '../base/lib-jitsi-meet';
import { MiddlewareRegistry } from '../base/redux';
+
import { _showPasswordDialog } from './actions';
/**
@@ -20,17 +28,60 @@ MiddlewareRegistry.register(store => next => action => {
if (action.conference
&& JitsiConferenceErrors.PASSWORD_REQUIRED === action.error) {
- // XXX temporary solution till we move the whole invite
- // logic in react
+ // XXX temporary solution while some components are not listening
+ // for lock state updates in redux
if (typeof APP !== 'undefined') {
- APP.conference.invite.setLockedFromElsewhere(true);
+ APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK, true);
}
store.dispatch(_showPasswordDialog(action.conference));
}
break;
}
+ case LOCK_STATE_CHANGED: {
+ // TODO Remove this logic when all components interested in the lock
+ // state change event are moved into react/redux.
+ if (typeof APP !== 'undefined') {
+ APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK, action.locked);
+ }
+
+ break;
+ }
+ case SET_PASSWORD_FAILED:
+ return _notifySetPasswordError(store, next, action);
}
return next(action);
});
+
+/**
+ * Handles errors that occur when a password is failed to be set.
+ *
+ * @param {Store} store - The Redux store in which the specified action is being
+ * dispatched.
+ * @param {Dispatch} next - The Redux dispatch function to dispatch the
+ * specified action to the specified store.
+ * @param {Action} action - The Redux action SET_PASSWORD_ERROR which has the
+ * error type that should be handled.
+ * @private
+ * @returns {Object} The new state that is the result of the reduction of the
+ * specified action.
+ */
+function _notifySetPasswordError(store, next, action) {
+ if (typeof APP !== 'undefined') {
+ // TODO remove this logic when displaying of error messages on web is
+ // handled through react/redux
+ if (action.error
+ === JitsiMeetJS.errors.conference.PASSWORD_NOT_SUPPORTED) {
+ logger.warn('room passwords not supported');
+ APP.UI.messageHandler.showError(
+ 'dialog.warning', 'dialog.passwordNotSupported');
+ } else {
+ logger.warn('setting password failed', action.error);
+ APP.UI.messageHandler.showError(
+ 'dialog.lockTitle', 'dialog.lockMessage');
+ }
+ }
+
+ return next(action);
+}
diff --git a/react/features/toolbox/components/Toolbox.native.js b/react/features/toolbox/components/Toolbox.native.js
index 6a3be5fce..a2aa182bd 100644
--- a/react/features/toolbox/components/Toolbox.native.js
+++ b/react/features/toolbox/components/Toolbox.native.js
@@ -322,7 +322,7 @@ function _mapStateToProps(state) {
* @protected
* @type {boolean}
*/
- _locked: conference.locked
+ _locked: Boolean(conference.locked)
};
}
diff --git a/react/features/toolbox/components/Toolbox.web.js b/react/features/toolbox/components/Toolbox.web.js
index 49cd770d5..da1cc4316 100644
--- a/react/features/toolbox/components/Toolbox.web.js
+++ b/react/features/toolbox/components/Toolbox.web.js
@@ -223,7 +223,6 @@ function _mapDispatchToProps(dispatch: Function): Object {
* @returns {{
* _alwaysVisible: boolean,
* _audioMuted: boolean,
- * _locked: boolean,
* _subjectSlideIn: boolean,
* _videoMuted: boolean
* }}
diff --git a/react/features/toolbox/defaultToolbarButtons.js b/react/features/toolbox/defaultToolbarButtons.js
index 7082c4ee5..3b0c92d4c 100644
--- a/react/features/toolbox/defaultToolbarButtons.js
+++ b/react/features/toolbox/defaultToolbarButtons.js
@@ -4,6 +4,8 @@ import React from 'react';
import UIEvents from '../../../service/UI/UIEvents';
+import { openInviteDialog } from '../invite';
+
declare var APP: Object;
declare var config: Object;
declare var JitsiMeetJS: Object;
@@ -222,7 +224,7 @@ export default {
id: 'toolbar_button_link',
onClick() {
JitsiMeetJS.analytics.sendEvent('toolbar.invite.clicked');
- APP.UI.emitEvent(UIEvents.INVITE_CLICKED);
+ APP.store.dispatch(openInviteDialog());
},
tooltipKey: 'toolbar.invite'
},
diff --git a/service/UI/UIEvents.js b/service/UI/UIEvents.js
index 8720e50b7..d11d499ed 100644
--- a/service/UI/UIEvents.js
+++ b/service/UI/UIEvents.js
@@ -22,10 +22,6 @@ export default {
VIDEO_MUTED: "UI.video_muted",
ETHERPAD_CLICKED: "UI.etherpad_clicked",
SHARED_VIDEO_CLICKED: "UI.start_shared_video",
- /**
- * Indicates that an invite button has been clicked.
- */
- INVITE_CLICKED: "UI.invite_clicked",
/**
* Updates shared video with params: url, state, time(optional)
* Where url is the video link, state is stop/start/pause and time is the