diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js
index 308c4baa6..72bc1ffbb 100644
--- a/app/actions/actionsTypes.js
+++ b/app/actions/actionsTypes.js
@@ -20,6 +20,10 @@ export const LOGIN = createRequestTypes('LOGIN', [
'SET_USERNAME_REQUEST',
'SET_USERNAME_SUCCESS'
]);
+export const FORGOT_PASSWORD = createRequestTypes('FORGOT_PASSWORD', [
+ ...defaultTypes,
+ 'INIT'
+]);
export const ROOMS = createRequestTypes('ROOMS');
export const APP = createRequestTypes('APP', ['READY', 'INIT']);
export const MESSAGES = createRequestTypes('MESSAGES');
diff --git a/app/actions/login.js b/app/actions/login.js
index 1a8f753e8..4a67acf84 100644
--- a/app/actions/login.js
+++ b/app/actions/login.js
@@ -81,3 +81,29 @@ export function logout() {
type: types.LOGOUT
};
}
+
+export function forgotPasswordInit() {
+ return {
+ type: types.FORGOT_PASSWORD.INIT
+ };
+}
+
+export function forgotPasswordRequest(email) {
+ return {
+ type: types.FORGOT_PASSWORD.REQUEST,
+ email
+ };
+}
+
+export function forgotPasswordSuccess() {
+ return {
+ type: types.FORGOT_PASSWORD.SUCCESS
+ };
+}
+
+export function forgotPasswordFailure(err) {
+ return {
+ type: types.FORGOT_PASSWORD.FAILURE,
+ err
+ };
+}
diff --git a/app/containers/routes/PublicRoutes.js b/app/containers/routes/PublicRoutes.js
index a99583cd2..92781c00d 100644
--- a/app/containers/routes/PublicRoutes.js
+++ b/app/containers/routes/PublicRoutes.js
@@ -7,6 +7,7 @@ import ListServerView from '../../views/ListServerView';
import NewServerView from '../../views/NewServerView';
import LoginView from '../../views/LoginView';
import RegisterView from '../../views/RegisterView';
+import ForgotPasswordView from '../../views/ForgotPasswordView';
const PublicRoutes = StackNavigator(
{
@@ -43,6 +44,12 @@ const PublicRoutes = StackNavigator(
navigationOptions: {
title: 'Register'
}
+ },
+ ForgotPassword: {
+ screen: ForgotPasswordView,
+ navigationOptions: {
+ title: 'Forgot my password'
+ }
}
},
{
diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js
index 139d94db8..81bc55f40 100644
--- a/app/lib/rocketchat.js
+++ b/app/lib/rocketchat.js
@@ -144,6 +144,17 @@ const RocketChat = {
});
},
+ forgotPassword(email) {
+ return new Promise((resolve, reject) => {
+ Meteor.call('sendForgotPasswordEmail', email, (err, result) => {
+ if (err) {
+ reject(err);
+ }
+ resolve(result);
+ });
+ });
+ },
+
loginWithPassword({ username, password, code }, callback) {
let params = {};
const state = reduxStore.getState();
diff --git a/app/reducers/login.js b/app/reducers/login.js
index 9a3c33de2..fc0d94227 100644
--- a/app/reducers/login.js
+++ b/app/reducers/login.js
@@ -69,6 +69,28 @@ export default function login(state = initialState, action) {
isFetching: false,
isRegistering: false
};
+ case types.FORGOT_PASSWORD.INIT:
+ return initialState;
+ case types.FORGOT_PASSWORD.REQUEST:
+ return {
+ ...state,
+ isFetching: true,
+ failure: false,
+ success: false
+ };
+ case types.FORGOT_PASSWORD.SUCCESS:
+ return {
+ ...state,
+ isFetching: false,
+ success: true
+ };
+ case types.FORGOT_PASSWORD.FAILURE:
+ return {
+ ...state,
+ isFetching: false,
+ failure: true,
+ error: action.err
+ };
default:
return state;
}
diff --git a/app/sagas/login.js b/app/sagas/login.js
index 90bee4648..c16e9f1b4 100644
--- a/app/sagas/login.js
+++ b/app/sagas/login.js
@@ -11,7 +11,9 @@ import {
logout,
registerSuccess,
setUsernameRequest,
- setUsernameSuccess
+ setUsernameSuccess,
+ forgotPasswordSuccess,
+ forgotPasswordFailure
} from '../actions/login';
import RocketChat from '../lib/rocketchat';
@@ -22,6 +24,7 @@ const loginCall = args => (args.resume ? RocketChat.login(args) : RocketChat.log
const registerCall = args => RocketChat.register(args);
const setUsernameCall = args => RocketChat.setUsername(args);
const logoutCall = args => RocketChat.logout(args);
+const forgotPasswordCall = args => RocketChat.forgotPassword(args);
const getToken = function* getToken() {
const currentServer = yield select(getServer);
@@ -138,6 +141,15 @@ const handleLogout = function* handleLogout() {
yield call(logoutCall, { server });
};
+const handleForgotPasswordRequest = function* handleForgotPasswordRequest({ email }) {
+ try {
+ yield call(forgotPasswordCall, email);
+ yield put(forgotPasswordSuccess());
+ } catch (err) {
+ yield put(forgotPasswordFailure(err));
+ }
+};
+
const root = function* root() {
yield takeEvery(types.SERVER.CHANGED, handleLoginWhenServerChanges);
yield takeLatest(types.LOGIN.REQUEST, handleLoginRequest);
@@ -149,5 +161,6 @@ const root = function* root() {
yield takeLatest(types.LOGIN.SET_USERNAME_SUBMIT, handleSetUsernameSubmit);
yield takeLatest(types.LOGIN.SET_USERNAME_REQUEST, handleSetUsernameRequest);
yield takeLatest(types.LOGOUT, handleLogout);
+ yield takeLatest(types.FORGOT_PASSWORD.REQUEST, handleForgotPasswordRequest);
};
export default root;
diff --git a/app/views/ForgotPasswordView.js b/app/views/ForgotPasswordView.js
new file mode 100644
index 000000000..ae26fbd2a
--- /dev/null
+++ b/app/views/ForgotPasswordView.js
@@ -0,0 +1,116 @@
+import React from 'react';
+import Spinner from 'react-native-loading-spinner-overlay';
+import PropTypes from 'prop-types';
+import { Text, TextInput, View, TouchableOpacity, Alert } from 'react-native';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+import * as loginActions from '../actions/login';
+import KeyboardView from '../presentation/KeyboardView';
+
+import styles from './Styles';
+
+class ForgotPasswordView extends React.Component {
+ static propTypes = {
+ forgotPasswordInit: PropTypes.func.isRequired,
+ forgotPasswordRequest: PropTypes.func.isRequired,
+ login: PropTypes.object,
+ navigation: PropTypes.object.isRequired
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ email: '',
+ invalidEmail: false
+ };
+ }
+
+ componentWillMount() {
+ this.props.forgotPasswordInit();
+ }
+
+ componentDidUpdate() {
+ const { login } = this.props;
+ if (login.success) {
+ this.props.navigation.goBack();
+ setTimeout(() => {
+ Alert.alert(
+ 'Alert',
+ 'If this email is registered, ' +
+ 'we\'ll send instructions on how to reset your password. ' +
+ 'If you do not receive an email shortly, please come back and try again.'
+ );
+ });
+ }
+ }
+
+ validate = (email) => {
+ /* eslint-disable no-useless-escape */
+ const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+ if (!reg.test(email)) {
+ this.setState({ invalidEmail: true });
+ return;
+ }
+ this.setState({ email, invalidEmail: false });
+ }
+
+ resetPassword = () => {
+ if (this.state.invalidEmail) {
+ return;
+ }
+ this.props.forgotPasswordRequest(this.state.email);
+ }
+
+ backLogin = () => {
+ this.props.navigation.goBack();
+ }
+
+ render() {
+ return (
+
+
+
+ this.validate(email)}
+ keyboardType='email-address'
+ autoCorrect={false}
+ returnKeyType='next'
+ autoCapitalize='none'
+ underlineColorAndroid='transparent'
+ onSubmitEditing={() => this.resetPassword()}
+ placeholder='Email'
+ />
+
+
+ RESET PASSWORD
+
+
+
+ BACK TO LOGIN
+
+
+ {this.props.login.failure && {this.props.login.error.reason}}
+
+
+
+
+ );
+ }
+}
+
+function mapStateToProps(state) {
+ return {
+ login: state.login
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return bindActionCreators(loginActions, dispatch);
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(ForgotPasswordView);
diff --git a/app/views/LoginView.js b/app/views/LoginView.js
index 2f259c3dc..f27741bc3 100644
--- a/app/views/LoginView.js
+++ b/app/views/LoginView.js
@@ -49,6 +49,10 @@ class LoginView extends React.Component {
this.props.navigation.navigate('Register');
}
+ forgotPassword = () => {
+ this.props.navigation.navigate('ForgotPassword');
+ }
+
renderTOTP = () => {
if (this.props.login.errorMessage && this.props.login.errorMessage.error === 'totp-required') {
return (
@@ -106,10 +110,14 @@ class LoginView extends React.Component {
LOGIN
-
+
REGISTER
+
+ FORGOT MY PASSWORD
+
+
{this.props.login.failure && {this.props.login.error.reason}}
diff --git a/app/views/Styles.js b/app/views/Styles.js
index 6fb77cb66..299739353 100644
--- a/app/views/Styles.js
+++ b/app/views/Styles.js
@@ -93,9 +93,6 @@ export default StyleSheet.create({
backgroundColor: '#1d74f5',
marginBottom: 20
},
- registerContainer: {
- marginBottom: 0
- },
button: {
textAlign: 'center',
color: 'white',