+
+ );
+ },
+
_renderUserInterfaceSettings: function() {
var client = MatrixClientPeg.get();
@@ -819,6 +843,8 @@ module.exports = React.createClass({
{accountJsx}
+ {this._renderReferral()}
+
{notification_area}
{this._renderUserInterfaceSettings()}
diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js
index 90140b3280..0fc0cac527 100644
--- a/src/components/structures/login/Registration.js
+++ b/src/components/structures/login/Registration.js
@@ -25,6 +25,7 @@ var ServerConfig = require("../../views/login/ServerConfig");
var MatrixClientPeg = require("../../../MatrixClientPeg");
var RegistrationForm = require("../../views/login/RegistrationForm");
var CaptchaForm = require("../../views/login/CaptchaForm");
+var RtsClient = require("../../../RtsClient");
var MIN_PASSWORD_LENGTH = 6;
@@ -47,23 +48,16 @@ module.exports = React.createClass({
defaultIsUrl: React.PropTypes.string,
brand: React.PropTypes.string,
email: React.PropTypes.string,
+ referrer: React.PropTypes.string,
username: React.PropTypes.string,
guestAccessToken: React.PropTypes.string,
- teamsConfig: React.PropTypes.shape({
+ teamServerConfig: React.PropTypes.shape({
// Email address to request new teams
- supportEmail: React.PropTypes.string,
- teams: React.PropTypes.arrayOf(React.PropTypes.shape({
- // The displayed name of the team
- "name": React.PropTypes.string,
- // The suffix with which every team email address ends
- "emailSuffix": React.PropTypes.string,
- // The rooms to use during auto-join
- "rooms": React.PropTypes.arrayOf(React.PropTypes.shape({
- "id": React.PropTypes.string,
- "autoJoin": React.PropTypes.bool,
- })),
- })).required,
+ supportEmail: React.PropTypes.string.isRequired,
+ // URL of the riot-team-server to get team configurations and track referrals
+ teamServerURL: React.PropTypes.string.isRequired,
}),
+ teamSelected: React.PropTypes.object,
defaultDeviceDisplayName: React.PropTypes.string,
@@ -75,6 +69,7 @@ module.exports = React.createClass({
getInitialState: function() {
return {
busy: false,
+ teamServerBusy: false,
errorText: null,
// We remember the values entered by the user because
// the registration form will be unmounted during the
@@ -90,6 +85,7 @@ module.exports = React.createClass({
},
componentWillMount: function() {
+ this._unmounted = false;
this.dispatcherRef = dis.register(this.onAction);
// attach this to the instance rather than this.state since it isn't UI
this.registerLogic = new Signup.Register(
@@ -103,10 +99,40 @@ module.exports = React.createClass({
this.registerLogic.setIdSid(this.props.idSid);
this.registerLogic.setGuestAccessToken(this.props.guestAccessToken);
this.registerLogic.recheckState();
+
+ if (
+ this.props.teamServerConfig &&
+ this.props.teamServerConfig.teamServerURL &&
+ !this._rtsClient
+ ) {
+ this._rtsClient = new RtsClient(this.props.teamServerConfig.teamServerURL);
+
+ this.setState({
+ teamServerBusy: true,
+ });
+ // GET team configurations including domains, names and icons
+ this._rtsClient.getTeamsConfig().then((data) => {
+ const teamsConfig = {
+ teams: data,
+ supportEmail: this.props.teamServerConfig.supportEmail,
+ };
+ console.log('Setting teams config to ', teamsConfig);
+ this.setState({
+ teamsConfig: teamsConfig,
+ teamServerBusy: false,
+ });
+ }, (err) => {
+ console.error('Error retrieving config for teams', err);
+ this.setState({
+ teamServerBusy: false,
+ });
+ });
+ }
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
+ this._unmounted = true;
},
componentDidMount: function() {
@@ -184,24 +210,41 @@ module.exports = React.createClass({
accessToken: response.access_token
});
- // Auto-join rooms
- if (self.props.teamsConfig && self.props.teamsConfig.teams) {
- for (let i = 0; i < self.props.teamsConfig.teams.length; i++) {
- let team = self.props.teamsConfig.teams[i];
- if (self.state.formVals.email.endsWith(team.emailSuffix)) {
- console.log("User successfully registered with team " + team.name);
+ if (
+ self._rtsClient &&
+ self.props.referrer &&
+ self.state.teamSelected
+ ) {
+ // Track referral, get team_token in order to retrieve team config
+ self._rtsClient.trackReferral(
+ self.props.referrer,
+ response.user_id,
+ self.state.formVals.email
+ ).then((data) => {
+ const teamToken = data.team_token;
+ // Store for use /w welcome pages
+ window.localStorage.setItem('mx_team_token', teamToken);
+
+ self._rtsClient.getTeam(teamToken).then((team) => {
+ console.log(
+ `User successfully registered with team ${team.name}`
+ );
if (!team.rooms) {
- break;
+ return;
}
+ // Auto-join rooms
team.rooms.forEach((room) => {
- if (room.autoJoin) {
- console.log("Auto-joining " + room.id);
- MatrixClientPeg.get().joinRoom(room.id);
+ if (room.auto_join && room.room_id) {
+ console.log(`Auto-joining ${room.room_id}`);
+ MatrixClientPeg.get().joinRoom(room.room_id);
}
});
- break;
- }
- }
+ }, (err) => {
+ console.error('Error getting team config', err);
+ });
+ }, (err) => {
+ console.error('Error tracking referral', err);
+ });
}
if (self.props.brand) {
@@ -273,7 +316,15 @@ module.exports = React.createClass({
});
},
+ onTeamSelected: function(teamSelected) {
+ if (!this._unmounted) {
+ this.setState({ teamSelected });
+ }
+ },
+
_getRegisterContentJsx: function() {
+ const Spinner = sdk.getComponent("elements.Spinner");
+
var currStep = this.registerLogic.getStep();
var registerStep;
switch (currStep) {
@@ -283,17 +334,23 @@ module.exports = React.createClass({
case "Register.STEP_m.login.dummy":
// NB. Our 'username' prop is specifically for upgrading
// a guest account
+ if (this.state.teamServerBusy) {
+ registerStep = ;
+ break;
+ }
registerStep = (
+ onRegisterClick={this.onFormSubmit}
+ onTeamSelected={this.onTeamSelected}
+ />
);
break;
case "Register.STEP_m.login.email.identity":
@@ -322,7 +379,6 @@ module.exports = React.createClass({
}
var busySpinner;
if (this.state.busy) {
- var Spinner = sdk.getComponent("elements.Spinner");
busySpinner = (
);
@@ -367,7 +423,7 @@ module.exports = React.createClass({
return (
-
+
{this._getRegisterContentJsx()}
diff --git a/src/components/views/dialogs/ChatInviteDialog.js b/src/components/views/dialogs/ChatInviteDialog.js
index 61503196e5..ca3b07aa00 100644
--- a/src/components/views/dialogs/ChatInviteDialog.js
+++ b/src/components/views/dialogs/ChatInviteDialog.js
@@ -14,17 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-var React = require("react");
-var classNames = require('classnames');
-var sdk = require("../../../index");
-var Invite = require("../../../Invite");
-var createRoom = require("../../../createRoom");
-var MatrixClientPeg = require("../../../MatrixClientPeg");
-var DMRoomMap = require('../../../utils/DMRoomMap');
-var rate_limited_func = require("../../../ratelimitedfunc");
-var dis = require("../../../dispatcher");
-var Modal = require('../../../Modal');
+import React from 'react';
+import classNames from 'classnames';
+import sdk from '../../../index';
+import { getAddressType, inviteMultipleToRoom } from '../../../Invite';
+import createRoom from '../../../createRoom';
+import MatrixClientPeg from '../../../MatrixClientPeg';
+import DMRoomMap from '../../../utils/DMRoomMap';
+import rate_limited_func from '../../../ratelimitedfunc';
+import dis from '../../../dispatcher';
+import Modal from '../../../Modal';
import AccessibleButton from '../elements/AccessibleButton';
+import q from 'q';
const TRUNCATE_QUERY_LIST = 40;
@@ -186,13 +187,17 @@ module.exports = React.createClass({
// If the query isn't a user we know about, but is a
// valid address, add an entry for that
if (queryList.length == 0) {
- const addrType = Invite.getAddressType(query);
+ const addrType = getAddressType(query);
if (addrType !== null) {
- queryList.push({
+ queryList[0] = {
addressType: addrType,
address: query,
isKnown: false,
- });
+ };
+ if (this._cancelThreepidLookup) this._cancelThreepidLookup();
+ if (addrType == 'email') {
+ this._lookupThreepid(addrType, query).done();
+ }
}
}
}
@@ -212,6 +217,7 @@ module.exports = React.createClass({
inviteList: inviteList,
queryList: [],
});
+ if (this._cancelThreepidLookup) this._cancelThreepidLookup();
};
},
@@ -229,6 +235,7 @@ module.exports = React.createClass({
inviteList: inviteList,
queryList: [],
});
+ if (this._cancelThreepidLookup) this._cancelThreepidLookup();
},
_getDirectMessageRoom: function(addr) {
@@ -266,7 +273,7 @@ module.exports = React.createClass({
if (this.props.roomId) {
// Invite new user to a room
var self = this;
- Invite.inviteMultipleToRoom(this.props.roomId, addrTexts)
+ inviteMultipleToRoom(this.props.roomId, addrTexts)
.then(function(addrs) {
var room = MatrixClientPeg.get().getRoom(self.props.roomId);
return self._showAnyInviteErrors(addrs, room);
@@ -300,7 +307,7 @@ module.exports = React.createClass({
var room;
createRoom().then(function(roomId) {
room = MatrixClientPeg.get().getRoom(roomId);
- return Invite.inviteMultipleToRoom(roomId, addrTexts);
+ return inviteMultipleToRoom(roomId, addrTexts);
})
.then(function(addrs) {
return self._showAnyInviteErrors(addrs, room);
@@ -380,7 +387,7 @@ module.exports = React.createClass({
},
_isDmChat: function(addrs) {
- if (addrs.length === 1 && Invite.getAddressType(addrs[0]) === "mx" && !this.props.roomId) {
+ if (addrs.length === 1 && getAddressType(addrs[0]) === "mx" && !this.props.roomId) {
return true;
} else {
return false;
@@ -408,7 +415,7 @@ module.exports = React.createClass({
_addInputToList: function() {
const addressText = this.refs.textinput.value.trim();
- const addrType = Invite.getAddressType(addressText);
+ const addrType = getAddressType(addressText);
const addrObj = {
addressType: addrType,
address: addressText,
@@ -432,9 +439,45 @@ module.exports = React.createClass({
inviteList: inviteList,
queryList: [],
});
+ if (this._cancelThreepidLookup) this._cancelThreepidLookup();
return inviteList;
},
+ _lookupThreepid: function(medium, address) {
+ let cancelled = false;
+ // Note that we can't safely remove this after we're done
+ // because we don't know that it's the same one, so we just
+ // leave it: it's replacing the old one each time so it's
+ // not like they leak.
+ this._cancelThreepidLookup = function() {
+ cancelled = true;
+ }
+
+ // wait a bit to let the user finish typing
+ return q.delay(500).then(() => {
+ if (cancelled) return null;
+ return MatrixClientPeg.get().lookupThreePid(medium, address);
+ }).then((res) => {
+ if (res === null || !res.mxid) return null;
+ if (cancelled) return null;
+
+ return MatrixClientPeg.get().getProfileInfo(res.mxid);
+ }).then((res) => {
+ if (res === null) return null;
+ if (cancelled) return null;
+ this.setState({
+ queryList: [{
+ // an InviteAddressType
+ addressType: medium,
+ address: address,
+ displayName: res.displayname,
+ avatarMxc: res.avatar_url,
+ isKnown: true,
+ }]
+ });
+ });
+ },
+
render: function() {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const AddressSelector = sdk.getComponent("elements.AddressSelector");
diff --git a/src/components/views/elements/AddressTile.js b/src/components/views/elements/AddressTile.js
index 01c1ed3255..18492d8ae6 100644
--- a/src/components/views/elements/AddressTile.js
+++ b/src/components/views/elements/AddressTile.js
@@ -94,14 +94,14 @@ export default React.createClass({
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const TintableSvg = sdk.getComponent("elements.TintableSvg");
+ const nameClasses = classNames({
+ "mx_AddressTile_name": true,
+ "mx_AddressTile_justified": this.props.justified,
+ });
+
let info;
let error = false;
if (address.addressType === "mx" && address.isKnown) {
- const nameClasses = classNames({
- "mx_AddressTile_name": true,
- "mx_AddressTile_justified": this.props.justified,
- });
-
const idClasses = classNames({
"mx_AddressTile_id": true,
"mx_AddressTile_justified": this.props.justified,
@@ -123,13 +123,21 @@ export default React.createClass({
{ this.props.address.address }
);
} else if (address.addressType === "email") {
- var emailClasses = classNames({
+ const emailClasses = classNames({
"mx_AddressTile_email": true,
"mx_AddressTile_justified": this.props.justified,
});
+ let nameNode = null;
+ if (address.displayName) {
+ nameNode =
{ address.displayName }
+ }
+
info = (
-
{ address.address }
+
+
{ address.address }
+ {nameNode}
+
);
} else {
error = true;
diff --git a/src/components/views/login/RegistrationForm.js b/src/components/views/login/RegistrationForm.js
index f8a0863f70..1cb8253812 100644
--- a/src/components/views/login/RegistrationForm.js
+++ b/src/components/views/login/RegistrationForm.js
@@ -44,8 +44,8 @@ module.exports = React.createClass({
teams: React.PropTypes.arrayOf(React.PropTypes.shape({
// The displayed name of the team
"name": React.PropTypes.string,
- // The suffix with which every team email address ends
- "emailSuffix": React.PropTypes.string,
+ // The domain of team email addresses
+ "domain": React.PropTypes.string,
})).required,
}),
@@ -117,9 +117,6 @@ module.exports = React.createClass({
_doSubmit: function() {
let email = this.refs.email.value.trim();
- if (this.state.selectedTeam) {
- email += "@" + this.state.selectedTeam.emailSuffix;
- }
var promise = this.props.onRegisterClick({
username: this.refs.username.value.trim() || this.props.guestUsername,
password: this.refs.password.value.trim(),
@@ -134,25 +131,6 @@ module.exports = React.createClass({
}
},
- onSelectTeam: function(teamIndex) {
- let team = this._getSelectedTeam(teamIndex);
- if (team) {
- this.refs.email.value = this.refs.email.value.split("@")[0];
- }
- this.setState({
- selectedTeam: team,
- showSupportEmail: teamIndex === "other",
- });
- },
-
- _getSelectedTeam: function(teamIndex) {
- if (this.props.teamsConfig &&
- this.props.teamsConfig.teams[teamIndex]) {
- return this.props.teamsConfig.teams[teamIndex];
- }
- return null;
- },
-
/**
* Returns true if all fields were valid last time
* they were validated.
@@ -167,20 +145,36 @@ module.exports = React.createClass({
return true;
},
+ _isUniEmail: function(email) {
+ return email.endsWith('.ac.uk') || email.endsWith('.edu');
+ },
+
validateField: function(field_id) {
var pwd1 = this.refs.password.value.trim();
var pwd2 = this.refs.passwordConfirm.value.trim();
switch (field_id) {
case FIELD_EMAIL:
- let email = this.refs.email.value;
- if (this.props.teamsConfig) {
- let team = this.state.selectedTeam;
- if (team) {
- email = email + "@" + team.emailSuffix;
- }
+ const email = this.refs.email.value;
+ if (this.props.teamsConfig && this._isUniEmail(email)) {
+ const matchingTeam = this.props.teamsConfig.teams.find(
+ (team) => {
+ return email.split('@').pop() === team.domain;
+ }
+ ) || null;
+ this.setState({
+ selectedTeam: matchingTeam,
+ showSupportEmail: !matchingTeam,
+ });
+ this.props.onTeamSelected(matchingTeam);
+ } else {
+ this.props.onTeamSelected(null);
+ this.setState({
+ selectedTeam: null,
+ showSupportEmail: false,
+ });
}
- let valid = email === '' || Email.looksValid(email);
+ const valid = email === '' || Email.looksValid(email);
this.markFieldValid(field_id, valid, "RegistrationForm.ERR_EMAIL_INVALID");
break;
case FIELD_USERNAME:
@@ -260,61 +254,35 @@ module.exports = React.createClass({
return cls;
},
- _renderEmailInputSuffix: function() {
- let suffix = null;
- if (!this.state.selectedTeam) {
- return suffix;
- }
- let team = this.state.selectedTeam;
- if (team) {
- suffix = "@" + team.emailSuffix;
- }
- return suffix;
- },
-
render: function() {
var self = this;
- var emailSection, teamSection, teamAdditionSupport, registerButton;
+ var emailSection, belowEmailSection, registerButton;
if (this.props.showEmail) {
- let emailSuffix = this._renderEmailInputSuffix();
emailSection = (
-
-
- {emailSuffix ? : null }
-
+
);
if (this.props.teamsConfig) {
- teamSection = (
-
- );
if (this.props.teamsConfig.supportEmail && this.state.showSupportEmail) {
- teamAdditionSupport = (
-
- If your team is not listed, email
+ belowEmailSection = (
+
+
+ to get your university signed up. Or continue to register with Riot to enjoy our open source platform.
+
+ );
+ } else if (this.state.selectedTeam) {
+ belowEmailSection = (
+
+ You are registering with {this.state.selectedTeam.name}
+