Forgot my password (#62)

* Forgot password working
This commit is contained in:
Diego Mello 2017-11-10 11:42:02 -02:00 committed by Guilherme Gazzo
parent 556cf0d813
commit 7027656f4b
9 changed files with 209 additions and 5 deletions

View File

@ -20,6 +20,10 @@ export const LOGIN = createRequestTypes('LOGIN', [
'SET_USERNAME_REQUEST', 'SET_USERNAME_REQUEST',
'SET_USERNAME_SUCCESS' 'SET_USERNAME_SUCCESS'
]); ]);
export const FORGOT_PASSWORD = createRequestTypes('FORGOT_PASSWORD', [
...defaultTypes,
'INIT'
]);
export const ROOMS = createRequestTypes('ROOMS'); export const ROOMS = createRequestTypes('ROOMS');
export const APP = createRequestTypes('APP', ['READY', 'INIT']); export const APP = createRequestTypes('APP', ['READY', 'INIT']);
export const MESSAGES = createRequestTypes('MESSAGES'); export const MESSAGES = createRequestTypes('MESSAGES');

View File

@ -81,3 +81,29 @@ export function logout() {
type: types.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
};
}

View File

@ -7,6 +7,7 @@ import ListServerView from '../../views/ListServerView';
import NewServerView from '../../views/NewServerView'; import NewServerView from '../../views/NewServerView';
import LoginView from '../../views/LoginView'; import LoginView from '../../views/LoginView';
import RegisterView from '../../views/RegisterView'; import RegisterView from '../../views/RegisterView';
import ForgotPasswordView from '../../views/ForgotPasswordView';
const PublicRoutes = StackNavigator( const PublicRoutes = StackNavigator(
{ {
@ -43,6 +44,12 @@ const PublicRoutes = StackNavigator(
navigationOptions: { navigationOptions: {
title: 'Register' title: 'Register'
} }
},
ForgotPassword: {
screen: ForgotPasswordView,
navigationOptions: {
title: 'Forgot my password'
}
} }
}, },
{ {

View File

@ -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) { loginWithPassword({ username, password, code }, callback) {
let params = {}; let params = {};
const state = reduxStore.getState(); const state = reduxStore.getState();

View File

@ -69,6 +69,28 @@ export default function login(state = initialState, action) {
isFetching: false, isFetching: false,
isRegistering: 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: default:
return state; return state;
} }

View File

@ -11,7 +11,9 @@ import {
logout, logout,
registerSuccess, registerSuccess,
setUsernameRequest, setUsernameRequest,
setUsernameSuccess setUsernameSuccess,
forgotPasswordSuccess,
forgotPasswordFailure
} from '../actions/login'; } from '../actions/login';
import RocketChat from '../lib/rocketchat'; 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 registerCall = args => RocketChat.register(args);
const setUsernameCall = args => RocketChat.setUsername(args); const setUsernameCall = args => RocketChat.setUsername(args);
const logoutCall = args => RocketChat.logout(args); const logoutCall = args => RocketChat.logout(args);
const forgotPasswordCall = args => RocketChat.forgotPassword(args);
const getToken = function* getToken() { const getToken = function* getToken() {
const currentServer = yield select(getServer); const currentServer = yield select(getServer);
@ -138,6 +141,15 @@ const handleLogout = function* handleLogout() {
yield call(logoutCall, { server }); 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() { const root = function* root() {
yield takeEvery(types.SERVER.CHANGED, handleLoginWhenServerChanges); yield takeEvery(types.SERVER.CHANGED, handleLoginWhenServerChanges);
yield takeLatest(types.LOGIN.REQUEST, handleLoginRequest); 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_SUBMIT, handleSetUsernameSubmit);
yield takeLatest(types.LOGIN.SET_USERNAME_REQUEST, handleSetUsernameRequest); yield takeLatest(types.LOGIN.SET_USERNAME_REQUEST, handleSetUsernameRequest);
yield takeLatest(types.LOGOUT, handleLogout); yield takeLatest(types.LOGOUT, handleLogout);
yield takeLatest(types.FORGOT_PASSWORD.REQUEST, handleForgotPasswordRequest);
}; };
export default root; export default root;

View File

@ -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 (
<KeyboardView
contentContainerStyle={styles.container}
keyboardVerticalOffset={128}
>
<View style={styles.loginView}>
<View style={styles.formContainer}>
<TextInput
style={[styles.input_white, this.state.invalidEmail ? { borderColor: 'red' } : {}]}
onChangeText={email => this.validate(email)}
keyboardType='email-address'
autoCorrect={false}
returnKeyType='next'
autoCapitalize='none'
underlineColorAndroid='transparent'
onSubmitEditing={() => this.resetPassword()}
placeholder='Email'
/>
<TouchableOpacity style={styles.buttonContainer} onPress={this.resetPassword}>
<Text style={styles.button}>RESET PASSWORD</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.buttonContainer} onPress={this.backLogin}>
<Text style={styles.button}>BACK TO LOGIN</Text>
</TouchableOpacity>
{this.props.login.failure && <Text style={styles.error}>{this.props.login.error.reason}</Text>}
</View>
<Spinner visible={this.props.login.isFetching} textContent={'Loading...'} textStyle={{ color: '#FFF' }} />
</View>
</KeyboardView>
);
}
}
function mapStateToProps(state) {
return {
login: state.login
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(loginActions, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(ForgotPasswordView);

View File

@ -49,6 +49,10 @@ class LoginView extends React.Component {
this.props.navigation.navigate('Register'); this.props.navigation.navigate('Register');
} }
forgotPassword = () => {
this.props.navigation.navigate('ForgotPassword');
}
renderTOTP = () => { renderTOTP = () => {
if (this.props.login.errorMessage && this.props.login.errorMessage.error === 'totp-required') { if (this.props.login.errorMessage && this.props.login.errorMessage.error === 'totp-required') {
return ( return (
@ -106,10 +110,14 @@ class LoginView extends React.Component {
<Text style={styles.button} onPress={this.submit}>LOGIN</Text> <Text style={styles.button} onPress={this.submit}>LOGIN</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity style={[styles.buttonContainer, styles.registerContainer]}> <TouchableOpacity style={styles.buttonContainer}>
<Text style={styles.button} onPress={this.register}>REGISTER</Text> <Text style={styles.button} onPress={this.register}>REGISTER</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity style={styles.buttonContainer} onPress={this.forgotPassword}>
<Text style={styles.button}>FORGOT MY PASSWORD</Text>
</TouchableOpacity>
{this.props.login.failure && <Text style={styles.error}>{this.props.login.error.reason}</Text>} {this.props.login.failure && <Text style={styles.error}>{this.props.login.error.reason}</Text>}
</View> </View>
<Spinner visible={this.props.login.isFetching} textContent={'Loading...'} textStyle={{ color: '#FFF' }} /> <Spinner visible={this.props.login.isFetching} textContent={'Loading...'} textStyle={{ color: '#FFF' }} />

View File

@ -93,9 +93,6 @@ export default StyleSheet.create({
backgroundColor: '#1d74f5', backgroundColor: '#1d74f5',
marginBottom: 20 marginBottom: 20
}, },
registerContainer: {
marginBottom: 0
},
button: { button: {
textAlign: 'center', textAlign: 'center',
color: 'white', color: 'white',