diff --git a/src/components/login/Registration.js b/src/components/login/Registration.js
new file mode 100644
index 000000000..8fda406d4
--- /dev/null
+++ b/src/components/login/Registration.js
@@ -0,0 +1,266 @@
+/*
+Copyright 2015 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.
+*/
+
+'use strict';
+
+var React = require('react');
+
+var sdk = require('matrix-react-sdk');
+var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
+var dis = require('matrix-react-sdk/lib/dispatcher');
+var ServerConfig = require("./ServerConfig");
+var RegistrationForm = require("./RegistrationForm");
+var CaptchaForm = require("matrix-react-sdk/lib/components/login/CaptchaForm");
+var Signup = require("matrix-react-sdk/lib/Signup");
+var MIN_PASSWORD_LENGTH = 6;
+
+module.exports = React.createClass({
+ displayName: 'Registration',
+
+ propTypes: {
+ onLoggedIn: React.PropTypes.func.isRequired,
+ clientSecret: React.PropTypes.string,
+ sessionId: React.PropTypes.string,
+ registrationUrl: React.PropTypes.string,
+ idSid: React.PropTypes.string,
+ hsUrl: React.PropTypes.string,
+ isUrl: React.PropTypes.string,
+ // registration shouldn't know or care how login is done.
+ onLoginClick: React.PropTypes.func.isRequired
+ },
+
+ getInitialState: function() {
+ return {
+ busy: false,
+ errorText: null,
+ enteredHomeserverUrl: this.props.hsUrl,
+ enteredIdentityServerUrl: this.props.isUrl
+ };
+ },
+
+ componentWillMount: function() {
+ 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(
+ this.props.hsUrl, this.props.isUrl
+ );
+ this.registerLogic.setClientSecret(this.props.clientSecret);
+ this.registerLogic.setSessionId(this.props.sessionId);
+ this.registerLogic.setRegistrationUrl(this.props.registrationUrl);
+ this.registerLogic.setIdSid(this.props.idSid);
+ this.registerLogic.recheckState();
+ },
+
+ componentWillUnmount: function() {
+ dis.unregister(this.dispatcherRef);
+ },
+
+ componentDidMount: function() {
+ // may have already done an HTTP hit (e.g. redirect from an email) so
+ // check for any pending response
+ var promise = this.registerLogic.getPromise();
+ if (promise) {
+ this.onProcessingRegistration(promise);
+ }
+ },
+
+ onHsUrlChanged: function(newHsUrl) {
+ this.registerLogic.setHomeserverUrl(newHsUrl);
+ },
+
+ onIsUrlChanged: function(newIsUrl) {
+ this.registerLogic.setIdentityServerUrl(newIsUrl);
+ },
+
+ onAction: function(payload) {
+ if (payload.action !== "registration_step_update") {
+ return;
+ }
+ this.forceUpdate(); // registration state has changed.
+ },
+
+ onFormSubmit: function(formVals) {
+ var self = this;
+ this.setState({
+ errorText: "",
+ busy: true
+ });
+ this.onProcessingRegistration(this.registerLogic.register(formVals));
+ },
+
+ // Promise is resolved when the registration process is FULLY COMPLETE
+ onProcessingRegistration: function(promise) {
+ var self = this;
+ promise.done(function(response) {
+ if (!response || !response.access_token) {
+ console.warn(
+ "FIXME: Register fulfilled without a final response, " +
+ "did you break the promise chain?"
+ );
+ // no matter, we'll grab it direct
+ response = self.registerLogic.getCredentials();
+ }
+ if (!response || !response.user_id || !response.access_token) {
+ console.error("Final response is missing keys.");
+ self.setState({
+ errorText: "There was a problem processing the response."
+ });
+ return;
+ }
+ self.props.onLoggedIn({
+ userId: response.user_id,
+ homeserverUrl: self.registerLogic.getHomeserverUrl(),
+ identityServerUrl: self.registerLogic.getIdentityServerUrl(),
+ accessToken: response.access_token
+ });
+ self.setState({
+ busy: false
+ });
+ }, function(err) {
+ if (err.message) {
+ self.setState({
+ errorText: err.message
+ });
+ }
+ self.setState({
+ busy: false
+ });
+ console.log(err);
+ });
+ },
+
+ onFormValidationFailed: function(errCode) {
+ var errMsg;
+ switch (errCode) {
+ case "RegistrationForm.ERR_PASSWORD_MISSING":
+ errMsg = "Missing password.";
+ break;
+ case "RegistrationForm.ERR_PASSWORD_MISMATCH":
+ errMsg = "Passwords don't match.";
+ break;
+ case "RegistrationForm.ERR_PASSWORD_LENGTH":
+ errMsg = `Password too short (min ${MIN_PASSWORD_LENGTH}).`;
+ break;
+ default:
+ console.error("Unknown error code: %s", errCode);
+ errMsg = "An unknown error occurred.";
+ break;
+ }
+ this.setState({
+ errorText: errMsg
+ });
+ },
+
+ onCaptchaLoaded: function(divIdName) {
+ this.registerLogic.tellStage("m.login.recaptcha", {
+ divId: divIdName
+ });
+ this.setState({
+ busy: false // requires user input
+ });
+ },
+
+ // TODO:
+ // This should really be a different component which MatrixChat then
+ // instantiates rather than having it pollute registration logic. There is
+ // no reason to wedge them together here. This function is currently NOT CALLED.
+ _getPostRegisterJsx: function() {
+ var ChangeDisplayName = sdk.getComponent('molecules.ChangeDisplayName');
+ var ChangeAvatar = sdk.getComponent('molecules.ChangeAvatar');
+ return (
+
+ Set a display name:
+
+ Upload an avatar:
+
+
+
+ );
+ },
+
+ _getRegisterContentJsx: function() {
+ var currStep = this.registerLogic.getStep();
+ var registerStep;
+ switch (currStep) {
+ case "Register.COMPLETE":
+ return; // this._getPostRegisterJsx();
+ case "Register.START":
+ case "Register.STEP_m.login.dummy":
+ registerStep = (
+
+ );
+ break;
+ case "Register.STEP_m.login.email.identity":
+ registerStep = (
+
+ Please check your email to continue registration.
+
+ );
+ break;
+ case "Register.STEP_m.login.recaptcha":
+ registerStep = (
+
+ );
+ break;
+ default:
+ console.error("Unknown register state: %s", currStep);
+ break;
+ }
+ var busySpinner;
+ if (this.state.busy) {
+ var Spinner = sdk.getComponent("atoms.Spinner");
+ busySpinner = (
+
+ );
+ }
+ return (
+
+ );
+ },
+
+ render: function() {
+ return (
+
+
+
+
+
+ {this._getRegisterContentJsx()}
+
+
+ );
+ }
+});
diff --git a/src/components/login/RegistrationForm.js b/src/components/login/RegistrationForm.js
new file mode 100644
index 000000000..3f9fb6ae1
--- /dev/null
+++ b/src/components/login/RegistrationForm.js
@@ -0,0 +1,126 @@
+/*
+Copyright 2015 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.
+*/
+
+'use strict';
+
+var React = require('react');
+var sdk = require('matrix-react-sdk')
+
+/**
+ * A pure UI component which displays a registration form.
+ */
+module.exports = React.createClass({
+ displayName: 'RegistrationForm',
+
+ propTypes: {
+ defaultEmail: React.PropTypes.string,
+ defaultUsername: React.PropTypes.string,
+ showEmail: React.PropTypes.bool,
+ minPasswordLength: React.PropTypes.number,
+ onError: React.PropTypes.func,
+ onRegisterClick: React.PropTypes.func // onRegisterClick(Object) => ?Promise
+ },
+
+ getDefaultProps: function() {
+ return {
+ showEmail: false,
+ minPasswordLength: 6,
+ onError: function(e) {
+ console.error(e);
+ }
+ };
+ },
+
+ getInitialState: function() {
+ return {
+ email: this.props.defaultEmail,
+ username: this.props.defaultUsername,
+ password: null,
+ passwordConfirm: null
+ };
+ },
+
+ onSubmit: function(ev) {
+ ev.preventDefault();
+
+ var pwd1 = this.refs.password.value.trim();
+ var pwd2 = this.refs.passwordConfirm.value.trim()
+
+ var errCode;
+ if (!pwd1 || !pwd2) {
+ errCode = "RegistrationForm.ERR_PASSWORD_MISSING";
+ }
+ else if (pwd1 !== pwd2) {
+ errCode = "RegistrationForm.ERR_PASSWORD_MISMATCH";
+ }
+ else if (pwd1.length < this.props.minPasswordLength) {
+ errCode = "RegistrationForm.ERR_PASSWORD_LENGTH";
+ }
+ if (errCode) {
+ this.props.onError(errCode);
+ return;
+ }
+
+ var promise = this.props.onRegisterClick({
+ username: this.refs.username.value.trim(),
+ password: pwd1,
+ email: this.refs.email.value.trim()
+ });
+
+ if (promise) {
+ ev.target.disabled = true;
+ promise.finally(function() {
+ ev.target.disabled = false;
+ });
+ }
+ },
+
+ render: function() {
+ var emailSection, registerButton;
+ if (this.props.showEmail) {
+ emailSection = (
+
+ );
+ }
+ if (this.props.onRegisterClick) {
+ registerButton = (
+
+ );
+ }
+
+ return (
+
+
+
+ );
+ }
+});
diff --git a/src/controllers/templates/Register.js b/src/controllers/templates/Register.js
deleted file mode 100644
index 5f88d5905..000000000
--- a/src/controllers/templates/Register.js
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
-Copyright 2015 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.
-*/
-
-'use strict';
-
-var extend = require('matrix-react-sdk/lib/extend');
-var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
-var BaseRegisterController = require('matrix-react-sdk/lib/controllers/templates/Register.js');
-
-var RegisterController = {};
-extend(RegisterController, BaseRegisterController);
-
-RegisterController.onRegistered = function(user_id, access_token) {
- MatrixClientPeg.replaceUsingAccessToken(
- this.state.hs_url, this.state.is_url, user_id, access_token
- );
-
- this.setState({
- step: 'profile',
- busy: true
- });
-
- var self = this;
- var cli = MatrixClientPeg.get();
- cli.getProfileInfo(cli.credentials.userId).done(function(result) {
- self.setState({
- avatarUrl: result.avatar_url,
- busy: false
- });
- },
- function(err) {
- console.err(err);
- self.setState({
- busy: false
- });
- });
-};
-
-RegisterController.onAccountReady = function() {
- if (this.props.onLoggedIn) {
- this.props.onLoggedIn();
- }
-};
-
-module.exports = RegisterController;
diff --git a/src/skins/vector/skindex.js b/src/skins/vector/skindex.js
index 45d60f730..a2b8d8cd3 100644
--- a/src/skins/vector/skindex.js
+++ b/src/skins/vector/skindex.js
@@ -85,6 +85,5 @@ skin['organisms.UserSettings'] = require('./views/organisms/UserSettings');
skin['organisms.ViewSource'] = require('./views/organisms/ViewSource');
skin['pages.CompatibilityPage'] = require('./views/pages/CompatibilityPage');
skin['pages.MatrixChat'] = require('./views/pages/MatrixChat');
-skin['templates.Register'] = require('./views/templates/Register');
-module.exports = skin;
\ No newline at end of file
+module.exports = skin;
diff --git a/src/skins/vector/views/pages/MatrixChat.js b/src/skins/vector/views/pages/MatrixChat.js
index 1a2ea288b..81268d5ad 100644
--- a/src/skins/vector/views/pages/MatrixChat.js
+++ b/src/skins/vector/views/pages/MatrixChat.js
@@ -23,7 +23,10 @@ var MatrixChatController = require('matrix-react-sdk/lib/controllers/pages/Matri
var dis = require('matrix-react-sdk/lib/dispatcher');
var Matrix = require("matrix-js-sdk");
+
var ContextualMenu = require("../../../../ContextualMenu");
+var Login = require("../../../../components/login/Login");
+var Registration = require("../../../../components/login/Registration");
var config = require("../../../../../config.json");
module.exports = React.createClass({
@@ -102,12 +105,15 @@ module.exports = React.createClass({
this.showScreen("register");
},
+ onLoginClick: function() {
+ this.showScreen("login");
+ },
+
render: function() {
var LeftPanel = sdk.getComponent('organisms.LeftPanel');
var RoomView = sdk.getComponent('organisms.RoomView');
var RightPanel = sdk.getComponent('organisms.RightPanel');
var UserSettings = sdk.getComponent('organisms.UserSettings');
- var Register = sdk.getComponent('templates.Register');
var CreateRoom = sdk.getComponent('organisms.CreateRoom');
var RoomDirectory = sdk.getComponent('organisms.RoomDirectory');
var MatrixToolbar = sdk.getComponent('molecules.MatrixToolbar');
@@ -172,14 +178,17 @@ module.exports = React.createClass({
);
} else if (this.state.screen == 'register') {
return (
-
+ onLoggedIn={this.onLoggedIn}
+ onLoginClick={this.onLoginClick} />
);
} else {
- var Login = require("../../../../components/login/Login");
return (
-
-
- );
- // XXX: clearly these should be separate organisms
- case 'stage_m.login.email.identity':
- return (
-
- Please check your email to continue registration.
-
- );
- case 'stage_m.login.recaptcha':
- return (
-
- This Home Server would like to make sure you are not a robot
-
-
- );
- }
- },
-
- registerContent: function() {
- if (this.state.busy) {
- var Loader = sdk.getComponent("atoms.Spinner");
- return (
-
- );
- } else if (this.state.step == 'profile') {
- var ChangeDisplayName = sdk.getComponent('molecules.ChangeDisplayName');
- var ChangeAvatar = sdk.getComponent('molecules.ChangeAvatar');
- return (
-
- Set a display name:
-
- Upload an avatar:
-
-
-
- );
- } else {
- return (
-
- );
- }
- },
-
- onBadFields: function(bad) {
- var keys = Object.keys(bad);
- var strings = [];
- for (var i = 0; i < keys.length; ++i) {
- switch (bad[keys[i]]) {
- case this.FieldErrors.PasswordMismatch:
- strings.push("Passwords don't match");
- break;
- case this.FieldErrors.Missing:
- strings.push("Missing "+keys[i]);
- break;
- case this.FieldErrors.TooShort:
- strings.push(keys[i]+" is too short");
- break;
- case this.FieldErrors.InUse:
- strings.push(keys[i]+" is already taken");
- break;
- case this.FieldErrors.Length:
- strings.push(keys[i] + " is not long enough.");
- break;
- default:
- console.error("Unhandled FieldError: %s", bad[keys[i]]);
- break;
- }
- }
- var errtxt = strings.join(', ');
- this.setState({
- errorText: errtxt
- });
- },
-
- render: function() {
- return (
-
-
-
-
-
- {this.registerContent()}
-
-
- );
- }
-});