Implement login action using matrix-js-sdk
Change-Id: Ie8e69cc995c86a90bf73e74b13e063f957aa2bce
This commit is contained in:
parent
e52bf651bb
commit
be7b761fff
@ -7,6 +7,7 @@
|
||||
// NOTE: initialState constant is necessary so that Rekit could auto add initial state when creating async actions.
|
||||
|
||||
const initialState = {
|
||||
mtx: null,
|
||||
};
|
||||
|
||||
export default initialState;
|
||||
|
@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Redirect } from 'react-router-dom'
|
||||
import matrixLogo from '../../images/matrix-logo.svg';
|
||||
import * as actions from './redux/actions';
|
||||
|
||||
@ -12,25 +12,96 @@ export class Login extends Component {
|
||||
actions: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
homeserver: 'https://matrix.org',
|
||||
username: '',
|
||||
password: '',
|
||||
submitted: false
|
||||
};
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
const { name, value } = e.target;
|
||||
this.setState({ [name]: value });
|
||||
}
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({ submitted: true });
|
||||
const { homeserver, username, password } = this.state;
|
||||
const { login } = this.props.actions;
|
||||
const { history } = this.props;
|
||||
if (homeserver && username && password) {
|
||||
login(homeserver, username, password).then(
|
||||
history.push("/user-admin/list")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { mtx } = this.props;
|
||||
const { loginPending } = this.props.home;
|
||||
const { homeserver, username, password, submitted } = this.state;
|
||||
return (
|
||||
<div className="home-login">
|
||||
{mtx && mtx.clientRunning &&
|
||||
<Redirect to="/user-admin/list" />
|
||||
}
|
||||
<header className="app-header">
|
||||
<img src={matrixLogo} className="app-logo" alt="logo" />
|
||||
<h1 className="app-title">Welcome to Synapse Admin</h1>
|
||||
</header>
|
||||
<form className="app-login">
|
||||
<form className="app-login" onSubmit={this.handleSubmit}>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Username:</th><td><input name="user"/></td>
|
||||
<th className={'form-group' + (submitted && !homeserver ? ' has-error' : '')}>
|
||||
<label htmlFor="username">Homeserver</label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="text" className="form-control" name="homeserver" value={homeserver} onChange={this.handleChange} />
|
||||
{submitted && !homeserver &&
|
||||
<div className="help-block">Homeserver is required</div>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Password:</th><td><input name="password" type="password"/></td>
|
||||
<th className={'form-group' + (submitted && !username ? ' has-error' : '')}>
|
||||
<label htmlFor="username">Username</label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="text" className="form-control" name="username" value={username} onChange={this.handleChange} />
|
||||
{submitted && !username &&
|
||||
<div className="help-block">Username is required</div>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className={'form-group' + (submitted && !password ? ' has-error' : '')}>
|
||||
<label htmlFor="password">Password</label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="password" className="form-control" name="password" value={password} onChange={this.handleChange} />
|
||||
{submitted && !password &&
|
||||
<div className="help-block">Password is required</div>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<Link to="/user-admin/list">Login</Link>
|
||||
<div className="form-group">
|
||||
<button className="btn btn-primary">Login</button>
|
||||
{loginPending &&
|
||||
<img alt="logging in" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" />
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
@ -40,6 +111,7 @@ export class Login extends Component {
|
||||
/* istanbul ignore next */
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
mtx: state.common.mtx,
|
||||
home: state.home,
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
export { login, dismissLoginError } from './login';
|
@ -0,0 +1,4 @@
|
||||
export const HOME_LOGIN_BEGIN = 'HOME_LOGIN_BEGIN';
|
||||
export const HOME_LOGIN_SUCCESS = 'HOME_LOGIN_SUCCESS';
|
||||
export const HOME_LOGIN_FAILURE = 'HOME_LOGIN_FAILURE';
|
||||
export const HOME_LOGIN_DISMISS_ERROR = 'HOME_LOGIN_DISMISS_ERROR';
|
@ -1,4 +1,6 @@
|
||||
const initialState = {
|
||||
loginPending: false,
|
||||
loginError: null,
|
||||
};
|
||||
|
||||
export default initialState;
|
||||
|
91
src/features/home/redux/login.js
Normal file
91
src/features/home/redux/login.js
Normal file
@ -0,0 +1,91 @@
|
||||
import {
|
||||
HOME_LOGIN_BEGIN,
|
||||
HOME_LOGIN_SUCCESS,
|
||||
HOME_LOGIN_FAILURE,
|
||||
HOME_LOGIN_DISMISS_ERROR,
|
||||
} from './constants';
|
||||
import Matrix from 'matrix-js-sdk';
|
||||
|
||||
export function login(homeserver, username, password) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: HOME_LOGIN_BEGIN,
|
||||
});
|
||||
|
||||
// Return a promise so that you could control UI flow without states in the store.
|
||||
// For example: after submit a form, you need to redirect the page to another when succeeds or show some errors message if fails.
|
||||
// It's hard to use state to manage it, but returning a promise allows you to easily achieve it.
|
||||
// e.g.: handleSubmit() { this.props.actions.submitForm(data).then(()=> {}).catch(() => {}); }
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
var state = getState();
|
||||
state.common.mtx = Matrix.createClient(homeserver);
|
||||
const doRequest = state.common.mtx.login("m.login.password", { "user": username, "password": password });
|
||||
doRequest.then(
|
||||
(res) => {
|
||||
state.common.mtx.startClient();
|
||||
dispatch({
|
||||
type: HOME_LOGIN_SUCCESS,
|
||||
data: res,
|
||||
});
|
||||
resolve(res);
|
||||
},
|
||||
// Use rejectHandler as the second argument so that render errors won't be caught.
|
||||
(err) => {
|
||||
dispatch({
|
||||
type: HOME_LOGIN_FAILURE,
|
||||
data: { error: err },
|
||||
});
|
||||
reject(err.message);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
return promise;
|
||||
};
|
||||
}
|
||||
|
||||
// Async action saves request error by default, this method is used to dismiss the error info.
|
||||
// If you don't want errors to be saved in Redux store, just ignore this method.
|
||||
export function dismissLoginError() {
|
||||
return {
|
||||
type: HOME_LOGIN_DISMISS_ERROR,
|
||||
};
|
||||
}
|
||||
|
||||
export function reducer(state, action) {
|
||||
switch (action.type) {
|
||||
case HOME_LOGIN_BEGIN:
|
||||
// Just after a request is sent
|
||||
return {
|
||||
...state,
|
||||
loginPending: true,
|
||||
loginError: null,
|
||||
};
|
||||
|
||||
case HOME_LOGIN_SUCCESS:
|
||||
// The request is success
|
||||
return {
|
||||
...state,
|
||||
loginPending: false,
|
||||
loginError: null,
|
||||
};
|
||||
|
||||
case HOME_LOGIN_FAILURE:
|
||||
// The request is failed
|
||||
return {
|
||||
...state,
|
||||
loginPending: false,
|
||||
loginError: action.data.error,
|
||||
};
|
||||
|
||||
case HOME_LOGIN_DISMISS_ERROR:
|
||||
// Dismiss the request failure error
|
||||
return {
|
||||
...state,
|
||||
loginError: null,
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
import initialState from './initialState';
|
||||
import { reducer as loginReducer } from './login';
|
||||
|
||||
const reducers = [
|
||||
loginReducer,
|
||||
];
|
||||
|
||||
export default function reducer(state = initialState, action) {
|
||||
|
97
tests/features/home/redux/login.test.js
Normal file
97
tests/features/home/redux/login.test.js
Normal file
@ -0,0 +1,97 @@
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import nock from 'nock';
|
||||
|
||||
import {
|
||||
HOME_LOGIN_BEGIN,
|
||||
HOME_LOGIN_SUCCESS,
|
||||
HOME_LOGIN_FAILURE,
|
||||
HOME_LOGIN_DISMISS_ERROR,
|
||||
} from '../../../../src/features/home/redux/constants';
|
||||
|
||||
import {
|
||||
login,
|
||||
dismissLoginError,
|
||||
reducer,
|
||||
} from '../../../../src/features/home/redux/login';
|
||||
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureMockStore(middlewares);
|
||||
|
||||
describe('home/redux/login', () => {
|
||||
afterEach(() => {
|
||||
nock.cleanAll();
|
||||
});
|
||||
|
||||
it('dispatches success action when login succeeds', () => {
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(login())
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0]).toHaveProperty('type', HOME_LOGIN_BEGIN);
|
||||
expect(actions[1]).toHaveProperty('type', HOME_LOGIN_SUCCESS);
|
||||
});
|
||||
});
|
||||
|
||||
it('dispatches failure action when login fails', () => {
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(login({ error: true }))
|
||||
.catch(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0]).toHaveProperty('type', HOME_LOGIN_BEGIN);
|
||||
expect(actions[1]).toHaveProperty('type', HOME_LOGIN_FAILURE);
|
||||
expect(actions[1]).toHaveProperty('data.error', expect.anything());
|
||||
});
|
||||
});
|
||||
|
||||
it('returns correct action by dismissLoginError', () => {
|
||||
const expectedAction = {
|
||||
type: HOME_LOGIN_DISMISS_ERROR,
|
||||
};
|
||||
expect(dismissLoginError()).toEqual(expectedAction);
|
||||
});
|
||||
|
||||
it('handles action type HOME_LOGIN_BEGIN correctly', () => {
|
||||
const prevState = { loginPending: false };
|
||||
const state = reducer(
|
||||
prevState,
|
||||
{ type: HOME_LOGIN_BEGIN }
|
||||
);
|
||||
expect(state).not.toBe(prevState); // should be immutable
|
||||
expect(state.loginPending).toBe(true);
|
||||
});
|
||||
|
||||
it('handles action type HOME_LOGIN_SUCCESS correctly', () => {
|
||||
const prevState = { loginPending: true };
|
||||
const state = reducer(
|
||||
prevState,
|
||||
{ type: HOME_LOGIN_SUCCESS, data: {} }
|
||||
);
|
||||
expect(state).not.toBe(prevState); // should be immutable
|
||||
expect(state.loginPending).toBe(false);
|
||||
});
|
||||
|
||||
it('handles action type HOME_LOGIN_FAILURE correctly', () => {
|
||||
const prevState = { loginPending: true };
|
||||
const state = reducer(
|
||||
prevState,
|
||||
{ type: HOME_LOGIN_FAILURE, data: { error: new Error('some error') } }
|
||||
);
|
||||
expect(state).not.toBe(prevState); // should be immutable
|
||||
expect(state.loginPending).toBe(false);
|
||||
expect(state.loginError).toEqual(expect.anything());
|
||||
});
|
||||
|
||||
it('handles action type HOME_LOGIN_DISMISS_ERROR correctly', () => {
|
||||
const prevState = { loginError: new Error('some error') };
|
||||
const state = reducer(
|
||||
prevState,
|
||||
{ type: HOME_LOGIN_DISMISS_ERROR }
|
||||
);
|
||||
expect(state).not.toBe(prevState); // should be immutable
|
||||
expect(state.loginError).toBe(null);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user