parent
556cf0d813
commit
7027656f4b
|
@ -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');
|
||||||
|
|
|
@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -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'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
|
@ -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' }} />
|
||||||
|
|
|
@ -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',
|
||||||
|
|
Loading…
Reference in New Issue