Added feature to register a new user
This commit is contained in:
parent
d55db0fca5
commit
dbf7c18b4e
|
@ -10,7 +10,7 @@ function createRequestTypes(base, types = defaultTypes) {
|
|||
}
|
||||
|
||||
// Login events
|
||||
export const LOGIN = createRequestTypes('LOGIN', [...defaultTypes, 'SET_TOKEN', 'SUBMIT']);
|
||||
export const LOGIN = createRequestTypes('LOGIN', [...defaultTypes, 'SET_TOKEN', 'SUBMIT', 'REGISTER_SUBMIT', 'REGISTER_REQUEST', 'REGISTER_SUCCESS']);
|
||||
export const ROOMS = createRequestTypes('ROOMS');
|
||||
export const APP = createRequestTypes('APP', ['READY', 'INIT']);
|
||||
export const MESSAGES = createRequestTypes('MESSAGES');
|
||||
|
|
|
@ -13,6 +13,25 @@ export function loginRequest(credentials) {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
export function registerSubmit(credentials) {
|
||||
return {
|
||||
type: types.LOGIN.REGISTER_SUBMIT,
|
||||
credentials
|
||||
};
|
||||
}
|
||||
export function registerRequest(credentials) {
|
||||
return {
|
||||
type: types.LOGIN.REGISTER_REQUEST,
|
||||
credentials
|
||||
};
|
||||
}
|
||||
export function registerSuccess() {
|
||||
return {
|
||||
type: types.LOGIN.REGISTER_SUCCESS
|
||||
};
|
||||
}
|
||||
|
||||
export function loginSuccess(user) {
|
||||
return {
|
||||
type: types.LOGIN.SUCCESS,
|
||||
|
|
|
@ -6,6 +6,7 @@ import Icon from 'react-native-vector-icons/FontAwesome';
|
|||
import ListServerView from '../../views/ListServerView';
|
||||
import NewServerView from '../../views/NewServerView';
|
||||
import LoginView from '../../views/LoginView';
|
||||
import RegisterView from '../../views/RegisterView';
|
||||
|
||||
const PublicRoutes = StackNavigator(
|
||||
{
|
||||
|
@ -36,6 +37,12 @@ const PublicRoutes = StackNavigator(
|
|||
navigationOptions: {
|
||||
title: 'Login'
|
||||
}
|
||||
},
|
||||
Register: {
|
||||
screen: RegisterView,
|
||||
navigationOptions: {
|
||||
title: 'Register'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Realm from 'realm';
|
||||
// import { AsyncStorage } from 'react-native';
|
||||
import { AsyncStorage } from 'react-native';
|
||||
|
||||
const serversSchema = {
|
||||
name: 'servers',
|
||||
|
|
|
@ -122,6 +122,21 @@ const RocketChat = {
|
|||
});
|
||||
},
|
||||
|
||||
register(params, callback) {
|
||||
return new Promise((resolve, reject) => {
|
||||
return Meteor.call('registerUser', params, (err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
if (typeof callback === 'function') {
|
||||
callback(err, result);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
loginWithPassword({ username, password, code }, callback) {
|
||||
let params = {};
|
||||
const state = reduxStore.getState();
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { KeyboardAvoidingView } from 'react-native';
|
||||
import { ViewPropTypes } from 'react-native';
|
||||
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
|
||||
|
||||
export default class KeyboardView extends React.PureComponent {
|
||||
static propTypes = {
|
||||
style: KeyboardAvoidingView.propTypes.style,
|
||||
style: ViewPropTypes.style,
|
||||
keyboardVerticalOffset: PropTypes.number,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
|
@ -14,9 +15,15 @@ export default class KeyboardView extends React.PureComponent {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<KeyboardAvoidingView style={this.props.style} behavior='padding' keyboardVerticalOffset={this.props.keyboardVerticalOffset}>
|
||||
<KeyboardAwareScrollView
|
||||
contentContainerStyle={this.props.style}
|
||||
behavior='position'
|
||||
extraHeight={this.props.keyboardVerticalOffset}
|
||||
keyboardDismissMode='interactive'
|
||||
keyboardShouldPersistTaps='always'
|
||||
>
|
||||
{this.props.children}
|
||||
</KeyboardAvoidingView>
|
||||
</KeyboardAwareScrollView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,20 @@ export default function login(state = initialState, action) {
|
|||
token: action.token,
|
||||
user: action.user
|
||||
};
|
||||
case types.LOGIN.REGISTER_SUBMIT:
|
||||
return {
|
||||
...state,
|
||||
isFetching: true,
|
||||
isAuthenticated: false,
|
||||
failure: false
|
||||
};
|
||||
case types.LOGIN.REGISTER_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
isFetching: false,
|
||||
isAuthenticated: false,
|
||||
failure: false
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { AsyncStorage } from 'react-native';
|
||||
import { take, put, call, takeEvery, select, all, race } from 'redux-saga/effects';
|
||||
import * as types from '../actions/actionsTypes';
|
||||
import { loginRequest, loginSuccess, loginFailure, setToken, logout } from '../actions/login';
|
||||
import { loginRequest, registerRequest, loginSuccess, registerSuccess, loginFailure, setToken, logout } from '../actions/login';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
|
||||
const TOKEN_KEY = 'reactnativemeteor_usertoken';
|
||||
const getUser = state => state.login;
|
||||
const getServer = state => state.server.server;
|
||||
const loginCall = args => (args.resume ? RocketChat.login(args) : RocketChat.loginWithPassword(args));
|
||||
const registerCall = args => RocketChat.register(args);
|
||||
|
||||
const getToken = function* getToken() {
|
||||
const currentServer = yield select(getServer);
|
||||
|
@ -86,10 +87,31 @@ const handleLoginSubmit = function* handleLoginSubmit({ credentials }) {
|
|||
});
|
||||
};
|
||||
|
||||
const handleRegisterRequest = function* handleRegisterRequest({ credentials }) {
|
||||
try {
|
||||
yield call(registerCall, credentials);
|
||||
yield put(registerSuccess());
|
||||
} catch (err) {
|
||||
yield put(loginFailure(err));
|
||||
}
|
||||
};
|
||||
|
||||
const handleRegisterSubmit = function* handleRegisterSubmit({ credentials }) {
|
||||
// put a login request
|
||||
yield put(registerRequest(credentials));
|
||||
// wait for a response
|
||||
yield race({
|
||||
success: take(types.LOGIN.SUCCESS),
|
||||
error: take(types.LOGIN.FAILURE)
|
||||
});
|
||||
};
|
||||
|
||||
const root = function* root() {
|
||||
yield takeEvery(types.SERVER.CHANGED, handleLoginWhenServerChanges);
|
||||
yield takeEvery(types.LOGIN.REQUEST, handleLoginRequest);
|
||||
yield takeEvery(types.LOGIN.SUCCESS, saveToken);
|
||||
yield takeEvery(types.LOGIN.SUBMIT, handleLoginSubmit);
|
||||
yield takeEvery(types.LOGIN.REGISTER_REQUEST, handleRegisterRequest);
|
||||
yield takeEvery(types.LOGIN.REGISTER_SUBMIT, handleRegisterSubmit);
|
||||
};
|
||||
export default root;
|
||||
|
|
|
@ -18,7 +18,8 @@ class LoginView extends React.Component {
|
|||
loginSubmit: PropTypes.func.isRequired,
|
||||
Accounts_EmailOrUsernamePlaceholder: PropTypes.string,
|
||||
Accounts_PasswordPlaceholder: PropTypes.string,
|
||||
login: PropTypes.object
|
||||
login: PropTypes.object,
|
||||
navigation: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
static navigationOptions = () => ({
|
||||
|
@ -44,6 +45,10 @@ class LoginView extends React.Component {
|
|||
Keyboard.dismiss();
|
||||
}
|
||||
|
||||
register = () => {
|
||||
this.props.navigation.navigate('Register');
|
||||
}
|
||||
|
||||
renderTOTP = () => {
|
||||
if (this.props.login.errorMessage && this.props.login.errorMessage.error === 'totp-required') {
|
||||
return (
|
||||
|
@ -68,7 +73,7 @@ class LoginView extends React.Component {
|
|||
<KeyboardView style={styles.container} keyboardVerticalOffset={128}>
|
||||
<View style={{ alignItems: 'center' }}>
|
||||
<Image
|
||||
style={styles.logo}
|
||||
style={styles.loginLogo}
|
||||
source={require('../images/logo.png')}
|
||||
/>
|
||||
</View>
|
||||
|
@ -82,7 +87,6 @@ class LoginView extends React.Component {
|
|||
autoCorrect={false}
|
||||
returnKeyType='next'
|
||||
autoCapitalize='none'
|
||||
autoFocus
|
||||
|
||||
underlineColorAndroid='transparent'
|
||||
onSubmitEditing={() => { this.password.focus(); }}
|
||||
|
@ -102,10 +106,17 @@ class LoginView extends React.Component {
|
|||
onSubmitEditing={this.submit}
|
||||
placeholder={this.props.Accounts_PasswordPlaceholder || 'Password'}
|
||||
/>
|
||||
|
||||
{this.renderTOTP()}
|
||||
|
||||
<TouchableOpacity style={styles.buttonContainer}>
|
||||
<Text style={styles.button} onPress={this.submit}>LOGIN</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity style={styles.buttonContainer}>
|
||||
<Text style={styles.button} onPress={this.register}>REGISTER</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{this.props.login.error && <Text style={styles.error}>{this.props.login.error}</Text>}
|
||||
</View>
|
||||
<Spinner visible={this.props.login.isFetching} textContent={'Loading...'} textStyle={{ color: '#FFF' }} />
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
import React from 'react';
|
||||
|
||||
import Spinner from 'react-native-loading-spinner-overlay';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import { Keyboard, Text, TextInput, View, Image, TouchableOpacity } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
// import * as actions from '../actions';
|
||||
import * as loginActions from '../actions/login';
|
||||
import KeyboardView from '../presentation/KeyboardView';
|
||||
// import { Keyboard } from 'react-native'
|
||||
|
||||
import styles from './Styles';
|
||||
|
||||
class RegisterView extends React.Component {
|
||||
static propTypes = {
|
||||
registerSubmit: PropTypes.func.isRequired,
|
||||
Accounts_NamePlaceholder: PropTypes.string,
|
||||
Accounts_EmailOrUsernamePlaceholder: PropTypes.string,
|
||||
Accounts_PasswordPlaceholder: PropTypes.string,
|
||||
Accounts_UsernamePlaceholder: PropTypes.string,
|
||||
Accounts_RepeatPasswordPlaceholder: PropTypes.string,
|
||||
login: PropTypes.object,
|
||||
navigation: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
name: '',
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
confirmPassword: ''
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!this.props.login.isFetching && prevProps.login.isFetching &&
|
||||
!this.props.login.failure) {
|
||||
this.props.navigation.goBack();
|
||||
}
|
||||
}
|
||||
|
||||
submit = () => {
|
||||
const { name, username, email, password, confirmPassword, code } = this.state;
|
||||
if (name.trim() === '' || email.trim() === '' || username.trim() === '' ||
|
||||
password.trim() === '' || confirmPassword.trim() === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.registerSubmit({ name, username, email, pass: password, code });
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
|
||||
renderTOTP = () => {
|
||||
if (this.props.login.errorMessage && this.props.login.errorMessage.error === 'totp-required') {
|
||||
return (
|
||||
<TextInput
|
||||
ref={ref => this.codeInput = ref}
|
||||
style={styles.input}
|
||||
onChangeText={code => this.setState({ code })}
|
||||
keyboardType='numeric'
|
||||
autoCorrect={false}
|
||||
returnKeyType='done'
|
||||
autoCapitalize='none'
|
||||
onSubmitEditing={this.submit}
|
||||
placeholder='Code'
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// {this.props.login.isFetching && <Text> LOGANDO</Text>}
|
||||
render() {
|
||||
return (
|
||||
<KeyboardView style={styles.container} keyboardVerticalOffset={150}>
|
||||
<View style={styles.loginView}>
|
||||
<View style={styles.formContainer}>
|
||||
<TextInput
|
||||
ref={(e) => { this.name = e; }}
|
||||
placeholderTextColor={'rgba(255,255,255,.2)'}
|
||||
style={styles.input}
|
||||
onChangeText={name => this.setState({ name })}
|
||||
autoCorrect={false}
|
||||
returnKeyType='next'
|
||||
autoCapitalize='words'
|
||||
|
||||
underlineColorAndroid='transparent'
|
||||
onSubmitEditing={() => { this.username.focus(); }}
|
||||
placeholder={this.props.Accounts_NamePlaceholder || 'Name'}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
ref={(e) => { this.username = e; }}
|
||||
placeholderTextColor={'rgba(255,255,255,.2)'}
|
||||
style={styles.input}
|
||||
onChangeText={username => this.setState({ username })}
|
||||
autoCorrect={false}
|
||||
returnKeyType='next'
|
||||
autoCapitalize='none'
|
||||
|
||||
underlineColorAndroid='transparent'
|
||||
onSubmitEditing={() => { this.email.focus(); }}
|
||||
placeholder={this.props.Accounts_UsernamePlaceholder || 'Username'}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
ref={(e) => { this.email = e; }}
|
||||
placeholderTextColor={'rgba(255,255,255,.2)'}
|
||||
style={styles.input}
|
||||
onChangeText={email => this.setState({ email })}
|
||||
keyboardType='email-address'
|
||||
autoCorrect={false}
|
||||
returnKeyType='next'
|
||||
autoCapitalize='none'
|
||||
|
||||
underlineColorAndroid='transparent'
|
||||
onSubmitEditing={() => { this.password.focus(); }}
|
||||
placeholder={this.props.Accounts_EmailOrUsernamePlaceholder || 'Email'}
|
||||
/>
|
||||
<TextInput
|
||||
ref={(e) => { this.password = e; }}
|
||||
placeholderTextColor={'rgba(255,255,255,.2)'}
|
||||
style={styles.input}
|
||||
onChangeText={password => this.setState({ password })}
|
||||
secureTextEntry
|
||||
autoCorrect={false}
|
||||
returnKeyType='next'
|
||||
autoCapitalize='none'
|
||||
|
||||
underlineColorAndroid='transparent'
|
||||
onSubmitEditing={() => { this.confirmPassword.focus(); }}
|
||||
placeholder={this.props.Accounts_PasswordPlaceholder || 'Password'}
|
||||
/>
|
||||
<TextInput
|
||||
ref={(e) => { this.confirmPassword = e; }}
|
||||
placeholderTextColor={'rgba(255,255,255,.2)'}
|
||||
style={styles.input}
|
||||
onChangeText={confirmPassword => this.setState({ confirmPassword })}
|
||||
secureTextEntry
|
||||
autoCorrect={false}
|
||||
returnKeyType='done'
|
||||
autoCapitalize='none'
|
||||
|
||||
underlineColorAndroid='transparent'
|
||||
onSubmitEditing={this.submit}
|
||||
placeholder={this.props.Accounts_RepeatPasswordPlaceholder || 'Repeat Password'}
|
||||
/>
|
||||
|
||||
{this.renderTOTP()}
|
||||
|
||||
<TouchableOpacity style={styles.buttonContainer}>
|
||||
<Text style={styles.button} onPress={this.submit}>REGISTER</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{this.props.login.error && <Text style={styles.error}>{this.props.login.error}</Text>}
|
||||
</View>
|
||||
<Spinner visible={this.props.login.isFetching} textContent={'Loading...'} textStyle={{ color: '#FFF' }} />
|
||||
</View>
|
||||
</KeyboardView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
// console.log(Object.keys(state));
|
||||
return {
|
||||
server: state.server.server,
|
||||
Accounts_UsernamePlaceholder: state.settings.Accounts_UsernamePlaceholder,
|
||||
Accounts_EmailOrUsernamePlaceholder: state.settings.Accounts_EmailOrUsernamePlaceholder,
|
||||
Accounts_PasswordPlaceholder: state.settings.Accounts_PasswordPlaceholder,
|
||||
Accounts_RepeatPasswordPlaceholder: state.settings.Accounts_RepeatPasswordPlaceholder,
|
||||
login: state.login
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators(loginActions, dispatch);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(RegisterView);
|
|
@ -39,6 +39,12 @@ export default StyleSheet.create({
|
|||
borderRadius: 5,
|
||||
resizeMode: 'contain'
|
||||
},
|
||||
loginLogo: {
|
||||
width: Dimensions.get('window').width - 80,
|
||||
height: Dimensions.get('window').width - 80,
|
||||
borderRadius: 5,
|
||||
resizeMode: 'contain'
|
||||
},
|
||||
formContainer: {
|
||||
// marginBottom: 20
|
||||
},
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
"react-native-fetch-blob": "^0.10.8",
|
||||
"react-native-image-picker": "^0.26.4",
|
||||
"react-native-img-cache": "^1.4.0",
|
||||
"react-native-keyboard-aware-scroll-view": "^0.3.0",
|
||||
"react-native-loading-spinner-overlay": "^0.5.2",
|
||||
"react-native-meteor": "^1.1.0",
|
||||
"react-native-modal": "^3.1.0",
|
||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -2055,7 +2055,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
|
|||
safe-buffer "^5.0.1"
|
||||
sha.js "^2.4.8"
|
||||
|
||||
create-react-class@^15.5.2:
|
||||
create-react-class@^15.5.2, create-react-class@^15.6.0:
|
||||
version "15.6.0"
|
||||
resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.0.tgz#ab448497c26566e1e29413e883207d57cfe7bed4"
|
||||
dependencies:
|
||||
|
@ -5729,6 +5729,14 @@ react-native-img-cache@^1.4.0:
|
|||
dependencies:
|
||||
crypto-js "^3.1.9-1"
|
||||
|
||||
react-native-keyboard-aware-scroll-view@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-keyboard-aware-scroll-view/-/react-native-keyboard-aware-scroll-view-0.3.0.tgz#b9d7b0d5b47d2bb4285fe50a3d274b10a3b5e1a7"
|
||||
dependencies:
|
||||
create-react-class "^15.6.0"
|
||||
prop-types "^15.5.10"
|
||||
react-timer-mixin "^0.13.3"
|
||||
|
||||
react-native-loading-spinner-overlay@^0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/react-native-loading-spinner-overlay/-/react-native-loading-spinner-overlay-0.5.2.tgz#b7bcd277476d596615fd7feee601789f9bdc7acc"
|
||||
|
@ -5943,7 +5951,7 @@ react-test-renderer@16.0.0-alpha.12:
|
|||
fbjs "^0.8.9"
|
||||
object-assign "^4.1.0"
|
||||
|
||||
react-timer-mixin@^0.13.2:
|
||||
react-timer-mixin@^0.13.2, react-timer-mixin@^0.13.3:
|
||||
version "0.13.3"
|
||||
resolved "https://registry.yarnpkg.com/react-timer-mixin/-/react-timer-mixin-0.13.3.tgz#0da8b9f807ec07dc3e854d082c737c65605b3d22"
|
||||
|
||||
|
|
Loading…
Reference in New Issue