diff --git a/src/actions/login.js b/src/actions/login.js
new file mode 100644
index 000000000..2f74c37bd
--- /dev/null
+++ b/src/actions/login.js
@@ -0,0 +1,18 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {LoginTypes as types} from 'constants';
+import Client from 'client/client_instance';
+import {bindClientFunc} from 'actions/helpers.js';
+
+export function login(loginId, password, mfaToken = '') {
+ return bindClientFunc(
+ Client.login,
+ types.LOGIN_REQUEST,
+ types.LOGIN_SUCCESS,
+ types.LOGIN_FAILURE,
+ loginId,
+ password,
+ mfaToken
+ );
+}
diff --git a/src/components/login.js b/src/components/login.js
new file mode 100644
index 000000000..dbe54fa47
--- /dev/null
+++ b/src/components/login.js
@@ -0,0 +1,105 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {Component, PropTypes} from 'react';
+
+import {bindActionCreators} from 'redux';
+import {connect} from 'react-redux';
+import {Image, StyleSheet, Text, TextInput, View} from 'react-native';
+import {Actions as Routes} from 'react-native-router-flux';
+
+import * as loginActions from 'actions/login';
+import Button from 'components/button';
+import ErrorText from 'components/error_text';
+import {GlobalStyles} from 'styles';
+import logo from 'images/logo.png';
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ flexDirection: 'column',
+ alignItems: 'center',
+ paddingTop: 200,
+ backgroundColor: 'white'
+ },
+ logo: {
+ marginBottom: 10
+ }
+});
+
+const propTypes = {
+ login: PropTypes.object.isRequired,
+ actions: PropTypes.object.isRequired
+};
+
+class Login extends Component {
+ static propTypes = propTypes;
+ state = {
+ loginId: '',
+ password: ''
+ };
+
+ componentWillReceiveProps(props) {
+ if (this.props.login.status === 'fetching' &&
+ props.login.status === 'fetched') {
+ Routes.SelectTeam(); // eslint-disable-line new-cap
+ }
+ }
+
+ signIn() {
+ if (this.props.login.status !== 'fetching') {
+ const {loginId, password} = this.state;
+ this.props.actions.login(loginId, password);
+ }
+ }
+
+ render() {
+ return (
+
+
+ {'Mattermost'}
+
+ {'All team communication in one place, searchable and accessible anywhere'}
+
+ this.setState({loginId})}
+ style={GlobalStyles.inputBox}
+ placeholder='Email or Username'
+ autoCorrect={false}
+ autoCapitalize='none'
+ />
+ this.setState({password})}
+ style={GlobalStyles.inputBox}
+ placeholder='Password'
+ autoCorrect={false}
+ autoCapitalize='none'
+ />
+
+ );
+ }
+}
+
+function mapStateToProps(state) {
+ return {
+ login: state.views.login
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators(loginActions, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Login);
diff --git a/src/components/select_server_view.js b/src/components/select_server_view.js
index 355aea3b2..b6c5ef3c0 100644
--- a/src/components/select_server_view.js
+++ b/src/components/select_server_view.js
@@ -49,7 +49,7 @@ class SelectServerView extends Component {
onClick = () => {
Client.setUrl(this.state.serverUrl);
- Routes.SelectTeam(); // eslint-disable-line new-cap
+ Routes.Login(); // eslint-disable-line new-cap
// this.props.getPing().then(() => {
// AsyncStorage.setItem('serverUrl', this.state.serverUrl, () => {
diff --git a/src/constants/index.js b/src/constants/index.js
index c8cbac365..f15df8940 100644
--- a/src/constants/index.js
+++ b/src/constants/index.js
@@ -1,11 +1,13 @@
import * as ChannelsTypes from './channels';
import * as DeviceTypes from './device';
import * as GeneralTypes from './general';
+import * as LoginTypes from './login';
import * as TeamsTypes from './teams';
export {
DeviceTypes,
GeneralTypes,
+ LoginTypes,
TeamsTypes,
ChannelsTypes
};
diff --git a/src/constants/login.js b/src/constants/login.js
new file mode 100644
index 000000000..9bf5c1b69
--- /dev/null
+++ b/src/constants/login.js
@@ -0,0 +1,3 @@
+export const LOGIN_REQUEST = 'LOGIN_REQUEST';
+export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
+export const LOGIN_FAILURE = 'LOGIN_FAILURE';
diff --git a/src/reducers/index.js b/src/reducers/index.js
index a8b1d5ba2..0af273394 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -6,6 +6,7 @@ import {combineReducers} from 'redux';
import channels from './channels';
import device from './device.js';
import general from './general.js';
+import login from './login';
import teams from './teams';
const entities = combineReducers({
@@ -15,7 +16,8 @@ const entities = combineReducers({
});
const views = combineReducers({
- device
+ device,
+ login
});
export default combineReducers({
diff --git a/src/reducers/login.js b/src/reducers/login.js
new file mode 100644
index 000000000..0505ca059
--- /dev/null
+++ b/src/reducers/login.js
@@ -0,0 +1,32 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {LoginTypes as types} from 'constants';
+
+const initState = {
+ status: 'not fetched',
+ error: null
+};
+
+export default function reduceLogin(state = initState, action) {
+ switch (action.type) {
+
+ case types.LOGIN_REQUEST:
+ return {...state,
+ status: 'fetching',
+ error: null
+ };
+ case types.LOGIN_SUCCESS:
+ return {...state,
+ status: 'fetched'
+ };
+ case types.LOGIN_FAILURE:
+ return {...state,
+ status: 'failed',
+ error: action.error
+ };
+
+ default:
+ return state;
+ }
+}
diff --git a/src/routes.js b/src/routes.js
index c0e37872f..63bb6b407 100644
--- a/src/routes.js
+++ b/src/routes.js
@@ -6,6 +6,7 @@ import React, {Component} from 'react';
import {Scene, Router} from 'react-native-router-flux';
import ChannelsList from 'components/channels_list';
+import Login from 'components/login';
import SelectServerView from 'components/select_server_view';
import SelectTeam from 'components/select_team';
@@ -19,6 +20,11 @@ export default class Routes extends Component {
component={ChannelsList}
title='Channels'
/>
+