From 7027656f4bf6fe2142f94461d0753a93743e1f27 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 10 Nov 2017 11:42:02 -0200 Subject: [PATCH] Forgot my password (#62) * Forgot password working --- app/actions/actionsTypes.js | 4 + app/actions/login.js | 26 ++++++ app/containers/routes/PublicRoutes.js | 7 ++ app/lib/rocketchat.js | 11 +++ app/reducers/login.js | 22 +++++ app/sagas/login.js | 15 +++- app/views/ForgotPasswordView.js | 116 ++++++++++++++++++++++++++ app/views/LoginView.js | 10 ++- app/views/Styles.js | 3 - 9 files changed, 209 insertions(+), 5 deletions(-) create mode 100644 app/views/ForgotPasswordView.js 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',