diff --git a/src/Invite.js b/src/Invite.js new file mode 100644 index 0000000000..3b52d6a1f4 --- /dev/null +++ b/src/Invite.js @@ -0,0 +1,45 @@ +/* +Copyright 2016 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import MatrixClientPeg from './MatrixClientPeg'; + +const emailRegex = /^\S+@\S+\.\S+$/; + +export function getAddressType(inputText) { + const isEmailAddress = /^\S+@\S+\.\S+$/.test(inputText); + const isMatrixId = inputText[0] === '@' && inputText.indexOf(":") > 0; + + // sanity check the input for user IDs + if (isEmailAddress) { + return 'email'; + } else if (isMatrixId) { + return 'mx'; + } else { + return null; + } +} + +export function inviteToRoom(roomId, addr) { + const addrType = getAddressType(addr); + + if (addrType == 'email') { + return MatrixClientPeg.get().inviteByEmail(roomId, addr); + } else if (addrType == 'mx') { + return MatrixClientPeg.get().invite(roomId, addr); + } else { + throw new Error('Unsupported address'); + } +} diff --git a/src/component-index.js b/src/component-index.js index 97f8882b82..d61192de33 100644 --- a/src/component-index.js +++ b/src/component-index.js @@ -48,6 +48,7 @@ module.exports.components['views.create_room.RoomAlias'] = require('./components module.exports.components['views.dialogs.DeactivateAccountDialog'] = require('./components/views/dialogs/DeactivateAccountDialog'); module.exports.components['views.dialogs.ErrorDialog'] = require('./components/views/dialogs/ErrorDialog'); module.exports.components['views.dialogs.LogoutPrompt'] = require('./components/views/dialogs/LogoutPrompt'); +module.exports.components['views.dialogs.MultiInviteDialog'] = require('./components/views/dialogs/MultiInviteDialog'); module.exports.components['views.dialogs.NeedToRegisterDialog'] = require('./components/views/dialogs/NeedToRegisterDialog'); module.exports.components['views.dialogs.QuestionDialog'] = require('./components/views/dialogs/QuestionDialog'); module.exports.components['views.dialogs.SetDisplayNameDialog'] = require('./components/views/dialogs/SetDisplayNameDialog'); diff --git a/src/components/views/dialogs/MultiInviteDialog.js b/src/components/views/dialogs/MultiInviteDialog.js new file mode 100644 index 0000000000..0d487c32c4 --- /dev/null +++ b/src/components/views/dialogs/MultiInviteDialog.js @@ -0,0 +1,199 @@ +/* +Copyright 2016 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; + +import {getAddressType, inviteToRoom} from '../../../Invite'; +import sdk from '../../../index'; + +export default class MultiInviteDialog extends React.Component { + constructor(props, context) { + super(props, context); + + this._onCancel = this._onCancel.bind(this); + this._startInviting = this._startInviting.bind(this); + + this.state = { + busy: false, + completionStates: [], // State of each address (invited or error) + errorTexts: [], // Textual error per address + done: false, + }; + for (let i = 0; i < this.props.inputs.length; ++i) { + const input = this.props.inputs[i]; + if (getAddressType(input) === null) { + this.state.completionStates[i] = 'error'; + this.state.errorTexts[i] = 'Unrecognised address'; + } + } + } + + _onCancel() { + this.props.onFinished(false); + } + + _startInviting() { + this.setState({ + completionStates: [], + busy: true, + done: false, + }); + this._inviteMore(0); + } + + _inviteMore(nextIndex) { + if (nextIndex == this.props.inputs.length) { + this.setState({ + busy: false, + done: true, + }); + return; + } + + const input = this.props.inputs[nextIndex]; + + // don't try to invite it if it's an invalid address + // (it will already be marked as an error though, + // so no need top do so again + if (getAddressType(input) === null) { + this._inviteMore(nextIndex + 1); + return; + } + + // don't re-invite (there's no way in the UI to do this, but + // for sanity's sake) + if (this.state.completionStates[nextIndex] == 'invited') { + this._inviteMore(nextIndex + 1); + return; + } + + inviteToRoom(this.props.roomId, input).then(() => { + this.setState((s) => { + s.completionStates[nextIndex] = 'invited' + return s; + }); + this._inviteMore(nextIndex + 1); + }, (err) => { + let errorText; + let fatal = false; + if (err.errcode == 'M_FORBIDDEN') { + fatal = true; + errorText = 'You do not have permission to invite people to this room.'; + } else if (err.errcode == 'M_LIMIT_EXCEEDED') { + // we're being throttled so wait a bit & try again + setTimeout(() => { + this._inviteMore(nextIndex); + }, 5000); + return; + } else { + errorText = 'Unknown server error'; + } + this.setState((s) => { + s.completionStates[nextIndex] = 'error'; + s.errorTexts[nextIndex] = errorText; + s.busy = !fatal; + s.done = fatal; + return s; + }); + if (!fatal) { + this._inviteMore(nextIndex + 1); + } + }); + } + + _getProgressIndicator() { + const numErrors = this.state.completionStates.filter((s) => { + return s == 'error'; + }).length; + let errorText; + if (numErrors > 0) { + const plural = numErrors > 1 ? 's' : ''; + errorText = ({numErrors} error{plural}) + } + return + {this.state.completionStates.length} / {this.props.inputs.length} {errorText} + ; + } + + render() { + const Spinner = sdk.getComponent("elements.Spinner"); + const inviteTiles = []; + + for (let i = 0; i < this.props.inputs.length; ++i) { + const input = this.props.inputs[i]; + let statusClass = ''; + let statusElement; + if (this.state.completionStates[i] == 'error') { + statusClass = 'error'; + statusElement =
{this.state.errorTexts[i]}
; + } else if (this.state.completionStates[i] == 'invited') { + statusClass = 'invited'; + } + inviteTiles.push( +{input}
+ {statusElement} +