From 125b880760897331393ed2acc4c2b1a41b84fa19 Mon Sep 17 00:00:00 2001 From: gilmarsquinelato <gilmarsquinelato@gmail.com> Date: Tue, 7 Nov 2017 14:28:02 -0200 Subject: [PATCH] Register user (#44) * Added feature to register a new user * login after register working * Removed username from register and placed on a new view * loading indicator on username submit * register/username logo layout issue * - login and register background white * - logo removed from logo and register --- app/actions/actionsTypes.js | 12 +- app/actions/login.js | 40 ++++++ app/containers/Routes.js | 20 +-- app/containers/routes/AuthRoutes.js | 9 +- app/containers/routes/PublicRoutes.js | 7 + app/images/logo_with_text.png | Bin 0 -> 27580 bytes app/lib/rocketchat.js | 22 +++ app/presentation/KeyboardView.js | 20 ++- app/presentation/Loading.js | 77 ++++++++++ app/reducers/login.js | 41 +++++- app/sagas/login.js | 71 ++++++++-- app/sagas/selectServer.js | 2 +- app/views/CreateChannelView.js | 99 ++++++------- app/views/LoginView.js | 38 ++--- app/views/NewServerView.js | 8 +- app/views/RegisterView.js | 194 ++++++++++++++++++++++++++ app/views/RoomView.js | 2 +- app/views/Styles.js | 36 ++--- package.json | 1 + yarn.lock | 12 +- 20 files changed, 580 insertions(+), 131 deletions(-) create mode 100644 app/images/logo_with_text.png create mode 100644 app/presentation/Loading.js create mode 100644 app/views/RegisterView.js diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js index ef66104a6..308c4baa6 100644 --- a/app/actions/actionsTypes.js +++ b/app/actions/actionsTypes.js @@ -9,7 +9,17 @@ 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', + 'SET_USERNAME_SUBMIT', + 'SET_USERNAME_REQUEST', + 'SET_USERNAME_SUCCESS' +]); 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 7969c8090..1a8f753e8 100644 --- a/app/actions/login.js +++ b/app/actions/login.js @@ -13,6 +13,46 @@ 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(credentials) { + return { + type: types.LOGIN.REGISTER_SUCCESS, + credentials + }; +} + +export function setUsernameSubmit(credentials) { + return { + type: types.LOGIN.SET_USERNAME_SUBMIT, + credentials + }; +} + +export function setUsernameRequest(credentials) { + return { + type: types.LOGIN.SET_USERNAME_REQUEST, + credentials + }; +} + +export function setUsernameSuccess() { + return { + type: types.LOGIN.SET_USERNAME_SUCCESS + }; +} + export function loginSuccess(user) { return { type: types.LOGIN.SUCCESS, diff --git a/app/containers/Routes.js b/app/containers/Routes.js index 485dd389d..83268096c 100644 --- a/app/containers/Routes.js +++ b/app/containers/Routes.js @@ -1,15 +1,12 @@ import PropTypes from 'prop-types'; import React from 'react'; -import { View, Image } from 'react-native'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; -import * as Animatable from 'react-native-animatable'; import { appInit } from '../actions'; -import styles from '../views/Styles'; - import AuthRoutes from './routes/AuthRoutes'; import PublicRoutes from './routes/PublicRoutes'; +import Loading from '../presentation/Loading'; @connect( state => ({ @@ -34,21 +31,10 @@ export default class Routes extends React.Component { const { login, app } = this.props; if (app.starting) { - return ( - <View style={styles.logoContainer}> - <Animatable.Text - animation='pulse' - easing='ease-out' - iterationCount='infinite' - style={{ textAlign: 'center' }} - > - <Image style={styles.logo} source={require('../images/logo.png')} /> - </Animatable.Text> - </View> - ); + return (<Loading />); } - if ((login.token && !login.failure) || app.ready) { + if ((login.token && !login.failure && !login.isRegistering) || app.ready) { return (<AuthRoutes />); } diff --git a/app/containers/routes/AuthRoutes.js b/app/containers/routes/AuthRoutes.js index 6bef85455..2c1a9f35e 100644 --- a/app/containers/routes/AuthRoutes.js +++ b/app/containers/routes/AuthRoutes.js @@ -1,7 +1,5 @@ import React from 'react'; -import { Button } from 'react-native'; -import { StackNavigator, DrawerNavigator, NavigationActions } from 'react-navigation'; -// import { Platform } from 'react-native'; +import { StackNavigator, DrawerNavigator, NavigationActions, HeaderBackButton } from 'react-navigation'; import Sidebar from '../../containers/Sidebar'; import DrawerMenuButton from '../../presentation/DrawerMenuButton'; @@ -39,7 +37,10 @@ const AuthRoutes = StackNavigator( return { title: navigation.state.params.title || 'Room', headerLeft: ( - <Button title={'Back'} onPress={() => backToScreen(navigation, 'RoomsList')} /> + <HeaderBackButton + title={'Back'} + onPress={() => backToScreen(navigation, 'RoomsList')} + /> ) // [drawerIconPosition]: (<DrawerMenuButton navigation={navigation} />)÷ }; diff --git a/app/containers/routes/PublicRoutes.js b/app/containers/routes/PublicRoutes.js index 3cf851f5c..a99583cd2 100644 --- a/app/containers/routes/PublicRoutes.js +++ b/app/containers/routes/PublicRoutes.js @@ -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' + } } }, { diff --git a/app/images/logo_with_text.png b/app/images/logo_with_text.png new file mode 100644 index 0000000000000000000000000000000000000000..623349a06ad0f2735cb64e6601481882ff4aa153 GIT binary patch literal 27580 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 P@i&O7wn~MPb;$n!1gNfk literal 0 HcmV?d00001 diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 71ddb447d..352eb00bb 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -122,6 +122,28 @@ const RocketChat = { }); }, + register({ credentials }) { + return new Promise((resolve, reject) => { + Meteor.call('registerUser', credentials, (err, userId) => { + if (err) { + reject(err); + } + resolve(userId); + }); + }); + }, + + setUsername({ credentials }) { + return new Promise((resolve, reject) => { + Meteor.call('setUsername', credentials.username, (err, result) => { + if (err) { + reject(err); + } + resolve(result); + }); + }); + }, + loginWithPassword({ username, password, code }, callback) { let params = {}; const state = reduxStore.getState(); diff --git a/app/presentation/KeyboardView.js b/app/presentation/KeyboardView.js index 3a6ea5f66..f3e29d9c2 100644 --- a/app/presentation/KeyboardView.js +++ b/app/presentation/KeyboardView.js @@ -1,11 +1,14 @@ 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, + contentContainerStyle: ViewPropTypes.style, keyboardVerticalOffset: PropTypes.number, + scrollEnabled: PropTypes.bool, children: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.node), PropTypes.node @@ -14,9 +17,18 @@ export default class KeyboardView extends React.PureComponent { render() { return ( - <KeyboardAvoidingView style={this.props.style} behavior='padding' keyboardVerticalOffset={this.props.keyboardVerticalOffset}> + <KeyboardAwareScrollView + keyboardDismissMode='interactive' + keyboardShouldPersistTaps='always' + style={this.props.style} + contentContainerStyle={this.props.contentContainerStyle} + scrollEnabled={this.props.scrollEnabled} + alwaysBounceVertical={false} + extraHeight={this.props.keyboardVerticalOffset} + behavior='position' + > {this.props.children} - </KeyboardAvoidingView> + </KeyboardAwareScrollView> ); } } diff --git a/app/presentation/Loading.js b/app/presentation/Loading.js new file mode 100644 index 000000000..ed92f1192 --- /dev/null +++ b/app/presentation/Loading.js @@ -0,0 +1,77 @@ +import React, { Component } from 'react'; +import { View, StyleSheet, Animated, Dimensions } from 'react-native'; + +const logo = require('../images/logo.png'); + + +const styles = StyleSheet.create({ + container: { + flex: 1, + width: '100%', + alignItems: 'center', + justifyContent: 'center' + }, + background: { + width: Dimensions.get('window').width, + height: Dimensions.get('window').height, + alignItems: 'center', + justifyContent: 'center' + }, + logo: { + width: Dimensions.get('window').width - 100, + height: Dimensions.get('window').width - 100, + resizeMode: 'contain' + } +}); + +export default class Loading extends Component { + constructor(props) { + super(props); + + this.scale = new Animated.Value(1.0); + } + + componentDidMount() { + requestAnimationFrame(() => { + this.animate(); + }); + } + + animate = () => { + Animated.sequence([ + Animated.timing( + this.scale, + { + toValue: 0.8, + duration: 1000, + useNativeDriver: true + }), + Animated.timing( + this.scale, + { + toValue: 1, + duration: 1000, + useNativeDriver: true + }) + ]).start(() => { + this.animate(); + }); + } + + render() { + return ( + <View style={styles.container}> + <Animated.Image + style={[ + styles.logo, + { + transform: [ + { scale: this.scale } + ] + }]} + source={logo} + /> + </View> + ); + } +} diff --git a/app/reducers/login.js b/app/reducers/login.js index 72f36d525..9a3c33de2 100644 --- a/app/reducers/login.js +++ b/app/reducers/login.js @@ -3,19 +3,20 @@ import * as types from '../actions/actionsTypes'; const initialState = { isAuthenticated: false, isFetching: false, + isRegistering: false, token: '', user: {}, - errorMessage: '' + error: '' }; export default function login(state = initialState, action) { switch (action.type) { case types.LOGIN.REQUEST: - console.log('types.LOGIN.REQUEST', action); return { ...state, isFetching: true, isAuthenticated: false, - failure: false + failure: false, + error: '' }; case types.LOGIN.SUCCESS: return { ...state, @@ -23,15 +24,15 @@ export default function login(state = initialState, action) { isAuthenticated: true, user: action.user, token: action.user.token, - failure: false - // user: action.user + failure: false, + error: '' }; case types.LOGIN.FAILURE: return { ...state, isFetching: false, isAuthenticated: false, failure: true, - errorMessage: action.err + error: action.err }; case types.LOGOUT: return initialState; @@ -40,6 +41,34 @@ export default function login(state = initialState, action) { token: action.token, user: action.user }; + case types.LOGIN.REGISTER_SUBMIT: + return { + ...state, + isFetching: true, + isAuthenticated: false, + isRegistering: true, + failure: false, + error: '' + }; + case types.LOGIN.REGISTER_SUCCESS: + return { + ...state, + isFetching: false, + isAuthenticated: false, + failure: false, + error: '' + }; + case types.LOGIN.SET_USERNAME_SUBMIT: + return { + ...state, + isFetching: true + }; + case types.LOGIN.SET_USERNAME_SUCCESS: + return { + ...state, + isFetching: false, + isRegistering: false + }; default: return state; } diff --git a/app/sagas/login.js b/app/sagas/login.js index 780655e7e..a8608e79d 100644 --- a/app/sagas/login.js +++ b/app/sagas/login.js @@ -1,13 +1,26 @@ import { AsyncStorage } from 'react-native'; -import { take, put, call, takeEvery, select, all, race } from 'redux-saga/effects'; +import { take, put, call, takeEvery, takeLatest, select, all } from 'redux-saga/effects'; import * as types from '../actions/actionsTypes'; -import { loginRequest, loginSuccess, loginFailure, setToken, logout } from '../actions/login'; +import { + loginRequest, + loginSubmit, + registerRequest, + loginSuccess, + loginFailure, + setToken, + logout, + registerSuccess, + setUsernameRequest, + setUsernameSuccess +} 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 setUsernameCall = args => RocketChat.setUsername(args); const getToken = function* getToken() { const currentServer = yield select(getServer); @@ -77,19 +90,57 @@ const handleLoginRequest = function* handleLoginRequest({ credentials }) { }; const handleLoginSubmit = function* handleLoginSubmit({ credentials }) { - // put a login request yield put(loginRequest(credentials)); +}; + +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) - }); + // yield race({ + // success: take(types.LOGIN.REGISTER_SUCCESS), + // error: take(types.LOGIN.FAILURE) + // }); +}; + +const handleRegisterRequest = function* handleRegisterRequest({ credentials }) { + try { + yield call(registerCall, { credentials }); + yield put(registerSuccess(credentials)); + } catch (err) { + yield put(loginFailure(err)); + } +}; + +const handleRegisterSuccess = function* handleRegisterSuccess({ credentials }) { + yield put(loginSubmit({ + username: credentials.email, + password: credentials.pass + })); +}; + +const handleSetUsernameSubmit = function* handleSetUsernameSubmit({ credentials }) { + yield put(setUsernameRequest(credentials)); +}; + +const handleSetUsernameRequest = function* handleSetUsernameRequest({ credentials }) { + try { + yield call(setUsernameCall, { credentials }); + yield put(setUsernameSuccess()); + } catch (err) { + yield put(loginFailure(err)); + } }; 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 takeLatest(types.LOGIN.REQUEST, handleLoginRequest); + yield takeLatest(types.LOGIN.SUCCESS, saveToken); + yield takeLatest(types.LOGIN.SUBMIT, handleLoginSubmit); + yield takeLatest(types.LOGIN.REGISTER_REQUEST, handleRegisterRequest); + yield takeLatest(types.LOGIN.REGISTER_SUBMIT, handleRegisterSubmit); + yield takeLatest(types.LOGIN.REGISTER_SUCCESS, handleRegisterSuccess); + yield takeLatest(types.LOGIN.SET_USERNAME_SUBMIT, handleSetUsernameSubmit); + yield takeLatest(types.LOGIN.SET_USERNAME_REQUEST, handleSetUsernameRequest); }; export default root; diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js index f03f2985f..dc9b039ec 100644 --- a/app/sagas/selectServer.js +++ b/app/sagas/selectServer.js @@ -32,7 +32,7 @@ const validateServer = function* validateServer({ server }) { }; const addServer = function* addServer({ server }) { - yield call(serverRequest, server); + yield put(serverRequest(server)); const { error } = yield race({ error: take(SERVER.FAILURE), diff --git a/app/views/CreateChannelView.js b/app/views/CreateChannelView.js index 01e5bb374..2041f8ab6 100644 --- a/app/views/CreateChannelView.js +++ b/app/views/CreateChannelView.js @@ -1,7 +1,7 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import { TextInput, View, Text, Switch, TouchableOpacity, ScrollView } from 'react-native'; +import { TextInput, View, Text, Switch, TouchableOpacity } from 'react-native'; import { createChannelRequest } from '../actions/createChannel'; import styles from './Styles'; import KeyboardView from '../presentation/KeyboardView'; @@ -90,55 +90,56 @@ export default class CreateChannelView extends React.Component { render() { return ( - <KeyboardView style={[styles.view_white, { flex: 1, justifyContent: 'flex-start' }]}> - <ScrollView> - <View style={styles.formContainer}> - <Text style={styles.label_white}>Channel Name</Text> - <TextInput - value={this.state.channelName} - style={styles.input_white} - onChangeText={channelName => this.setState({ channelName })} - autoCorrect={false} - returnKeyType='done' - autoCapitalize='none' - autoFocus - // onSubmitEditing={() => this.textInput.focus()} - placeholder='Type the channel name here' - /> - {this.renderChannelNameError()} - {this.renderTypeSwitch()} - <Text - style={[ - styles.label_white, - { - color: '#9ea2a8', - flexGrow: 1, - paddingHorizontal: 0, - marginBottom: 20 - } - ]} - > - {this.state.type ? ( - 'Everyone can access this channel' - ) : ( - 'Just invited people can access this channel' - )} + <KeyboardView + style={[styles.defaultViewBackground, { flex: 1 }]} + contentContainerStyle={styles.defaultView} + > + <View style={styles.formContainer}> + <Text style={styles.label_white}>Channel Name</Text> + <TextInput + value={this.state.channelName} + style={styles.input_white} + onChangeText={channelName => this.setState({ channelName })} + autoCorrect={false} + returnKeyType='done' + autoCapitalize='none' + autoFocus + // onSubmitEditing={() => this.textInput.focus()} + placeholder='Type the channel name here' + /> + {this.renderChannelNameError()} + {this.renderTypeSwitch()} + <Text + style={[ + styles.label_white, + { + color: '#9ea2a8', + flexGrow: 1, + paddingHorizontal: 0, + marginBottom: 20 + } + ]} + > + {this.state.type ? ( + 'Everyone can access this channel' + ) : ( + 'Just invited people can access this channel' + )} + </Text> + <TouchableOpacity + onPress={() => this.submit()} + style={[ + styles.buttonContainer_white, + this.state.channelName.length === 0 || this.props.result.isFetching + ? styles.disabledButton + : styles.enabledButton + ]} + > + <Text style={styles.button_white}> + {this.props.result.isFetching ? 'LOADING' : 'CREATE'}! </Text> - <TouchableOpacity - onPress={() => this.submit()} - style={[ - styles.buttonContainer_white, - this.state.channelName.length === 0 || this.props.result.isFetching - ? styles.disabledButton - : styles.enabledButton - ]} - > - <Text style={styles.button_white}> - {this.props.result.isFetching ? 'LOADING' : 'CREATE'}! - </Text> - </TouchableOpacity> - </View> - </ScrollView> + </TouchableOpacity> + </View> </KeyboardView> ); } diff --git a/app/views/LoginView.js b/app/views/LoginView.js index 7f182b7cb..2f259c3dc 100644 --- a/app/views/LoginView.js +++ b/app/views/LoginView.js @@ -3,7 +3,7 @@ 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 { Keyboard, Text, TextInput, View, TouchableOpacity } from 'react-native'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; // import * as actions from '../actions'; @@ -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 ( @@ -65,48 +70,47 @@ class LoginView extends React.Component { // {this.props.login.isFetching && <Text> LOGANDO</Text>} render() { return ( - <KeyboardView style={styles.container} keyboardVerticalOffset={128}> - <View style={{ alignItems: 'center' }}> - <Image - style={styles.logo} - source={require('../images/logo.png')} - /> - </View> + <KeyboardView + contentContainerStyle={styles.container} + keyboardVerticalOffset={128} + > <View style={styles.loginView}> <View style={styles.formContainer}> <TextInput - placeholderTextColor={'rgba(255,255,255,.2)'} - style={styles.input} + style={styles.input_white} onChangeText={username => this.setState({ username })} keyboardType='email-address' autoCorrect={false} returnKeyType='next' autoCapitalize='none' - autoFocus - underlineColorAndroid='transparent' onSubmitEditing={() => { this.password.focus(); }} placeholder={this.props.Accounts_EmailOrUsernamePlaceholder || 'Email or username'} /> <TextInput ref={(e) => { this.password = e; }} - placeholderTextColor={'rgba(255,255,255,.2)'} - style={styles.input} + style={styles.input_white} onChangeText={password => this.setState({ password })} secureTextEntry autoCorrect={false} returnKeyType='done' autoCapitalize='none' - underlineColorAndroid='transparent' 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> - {this.props.login.error && <Text style={styles.error}>{this.props.login.error}</Text>} + + <TouchableOpacity style={[styles.buttonContainer, styles.registerContainer]}> + <Text style={styles.button} onPress={this.register}>REGISTER</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> diff --git a/app/views/NewServerView.js b/app/views/NewServerView.js index 62ce32643..f022bc4ee 100644 --- a/app/views/NewServerView.js +++ b/app/views/NewServerView.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Text, TextInput, View, StyleSheet } from 'react-native'; +import { Text, TextInput, View, StyleSheet, Dimensions } from 'react-native'; import { connect } from 'react-redux'; import { serverRequest, addServer } from '../actions/server'; import KeyboardView from '../presentation/KeyboardView'; @@ -148,7 +148,11 @@ export default class NewServerView extends React.Component { render() { return ( - <KeyboardView style={styles.view} keyboardVerticalOffset={64}> + <KeyboardView + scrollEnabled={false} + contentContainerStyle={[styles.view, { height: Dimensions.get('window').height }]} + keyboardVerticalOffset={128} + > <View style={styles.spaceView} /> <TextInput ref={ref => this.inputElement = ref} diff --git a/app/views/RegisterView.js b/app/views/RegisterView.js new file mode 100644 index 000000000..656605727 --- /dev/null +++ b/app/views/RegisterView.js @@ -0,0 +1,194 @@ +import React from 'react'; + +import Spinner from 'react-native-loading-spinner-overlay'; + +import PropTypes from 'prop-types'; +import { Keyboard, Text, TextInput, View, TouchableOpacity } 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'; + +const placeholderTextColor = 'rgba(255,255,255,.2)'; + +class RegisterView extends React.Component { + static propTypes = { + registerSubmit: PropTypes.func.isRequired, + setUsernameSubmit: PropTypes.func, + Accounts_UsernamePlaceholder: PropTypes.string, + Accounts_NamePlaceholder: PropTypes.string, + Accounts_EmailOrUsernamePlaceholder: PropTypes.string, + Accounts_PasswordPlaceholder: PropTypes.string, + Accounts_RepeatPasswordPlaceholder: PropTypes.string, + login: PropTypes.object + } + + constructor(props) { + super(props); + + this.state = { + name: '', + email: '', + password: '', + confirmPassword: '' + }; + } + + _valid() { + const { name, email, password, confirmPassword } = this.state; + return name.trim() && email.trim() && + password && confirmPassword && password === confirmPassword; + } + _invalidEmail() { + return this.props.login.failure && /Email/.test(this.props.login.error.reason); + } + submit = () => { + const { name, email, password, code } = this.state; + if (!this._valid()) { + return; + } + + this.props.registerSubmit({ name, email, pass: password, code }); + Keyboard.dismiss(); + } + usernameSubmit = () => { + const { username } = this.state; + if (!username) { + return; + } + + this.props.setUsernameSubmit({ username }); + Keyboard.dismiss(); + } + + _renderRegister() { + if (this.props.login.token) { + return null; + } + return ( + <View style={styles.formContainer}> + <TextInput + ref={(e) => { this.name = e; }} + style={styles.input_white} + onChangeText={name => this.setState({ name })} + autoCorrect={false} + autoFocus + returnKeyType='next' + autoCapitalize='none' + underlineColorAndroid='transparent' + onSubmitEditing={() => { this.email.focus(); }} + placeholder={this.props.Accounts_NamePlaceholder || 'Name'} + /> + + <TextInput + ref={(e) => { this.email = e; }} + style={[styles.input_white, this._invalidEmail() ? { borderColor: 'red' } : {}]} + 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; }} + style={styles.input_white} + 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; }} + style={[styles.input_white, this.state.password && this.state.confirmPassword && this.state.confirmPassword !== this.state.password ? { borderColor: 'red' } : {}]} + onChangeText={confirmPassword => this.setState({ confirmPassword })} + secureTextEntry + autoCorrect={false} + returnKeyType='done' + autoCapitalize='none' + underlineColorAndroid='transparent' + onSubmitEditing={this.submit} + placeholder={this.props.Accounts_RepeatPasswordPlaceholder || 'Repeat Password'} + /> + + <TouchableOpacity style={[styles.buttonContainer, styles.registerContainer]}> + <Text + style={[styles.button, this._valid() ? {} + : { color: placeholderTextColor } + ]} + onPress={this.submit} + >REGISTER</Text> + </TouchableOpacity> + + {this.props.login.failure && <Text style={styles.error}>{this.props.login.error.reason}</Text>} + </View> + ); + } + + _renderUsername() { + if (!this.props.login.token) { + return null; + } + return ( + <View style={styles.formContainer}> + <TextInput + ref={(e) => { this.username = e; }} + style={styles.input_white} + onChangeText={username => this.setState({ username })} + autoCorrect={false} + returnKeyType='next' + autoCapitalize='none' + underlineColorAndroid='transparent' + onSubmitEditing={() => { this.usernameSubmit(); }} + placeholder={this.props.Accounts_UsernamePlaceholder || 'Username'} + /> + + <TouchableOpacity style={[styles.buttonContainer, styles.registerContainer]}> + <Text + style={styles.button} + onPress={this.usernameSubmit} + >REGISTER</Text> + </TouchableOpacity> + + {this.props.login.failure && <Text style={styles.error}>{this.props.login.error.reason}</Text>} + </View> + ); + } + + render() { + return ( + <KeyboardView contentContainerStyle={styles.container}> + <View style={styles.loginView}> + {this._renderRegister()} + {this._renderUsername()} + <Spinner visible={this.props.login.isFetching} textContent={'Loading...'} textStyle={{ color: '#FFF' }} /> + </View> + </KeyboardView> + ); + } +} + +function mapStateToProps(state) { + return { + server: state.server.server, + Accounts_NamePlaceholder: state.settings.Accounts_NamePlaceholder, + 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); diff --git a/app/views/RoomView.js b/app/views/RoomView.js index 3f6b473cc..6d1ee4f98 100644 --- a/app/views/RoomView.js +++ b/app/views/RoomView.js @@ -193,7 +193,7 @@ export default class RoomView extends React.Component { render() { return ( - <KeyboardView style={styles.container} keyboardVerticalOffset={64}> + <KeyboardView contentContainerStyle={styles.container} keyboardVerticalOffset={64}> {this.renderBanner()} <ListView enableEmptySections diff --git a/app/views/Styles.js b/app/views/Styles.js index c4c040baa..6fb77cb66 100644 --- a/app/views/Styles.js +++ b/app/views/Styles.js @@ -2,11 +2,8 @@ import { StyleSheet, Dimensions } from 'react-native'; export default StyleSheet.create({ container: { - flex: 1, - backgroundColor: '#2f343d', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'stretch' + backgroundColor: 'white', + flex: 1 }, loginView: { padding: 20 @@ -19,29 +16,31 @@ export default StyleSheet.create({ alignItems: 'stretch', backgroundColor: '#2f343d' }, - view_white: { - flex: 1, + defaultView: { flexDirection: 'column', justifyContent: 'center', padding: 20, - alignItems: 'stretch', + alignItems: 'stretch' + }, + defaultViewBackground: { backgroundColor: '#fff' }, logoContainer: { - flex: 1, alignItems: 'center', - flexGrow: 1, - justifyContent: 'center' + justifyContent: 'center', + flex: 1 }, - logo: { - width: Dimensions.get('window').width - 30, - height: Dimensions.get('window').width - 30, - borderRadius: 5, + loginLogo: { + width: Dimensions.get('window').width - 150, + height: Dimensions.get('window').width - 150, resizeMode: 'contain' }, - formContainer: { - // marginBottom: 20 + registerLogo: { + width: Dimensions.get('window').width - 40, + height: 100, + resizeMode: 'contain' }, + formContainer: {}, label: { lineHeight: 40, height: 40, @@ -94,6 +93,9 @@ export default StyleSheet.create({ backgroundColor: '#1d74f5', marginBottom: 20 }, + registerContainer: { + marginBottom: 0 + }, button: { textAlign: 'center', color: 'white', diff --git a/package.json b/package.json index 4a1688e05..2e1dbb944 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/yarn.lock b/yarn.lock index 8ad749b55..aa5ba2d71 100644 --- a/yarn.lock +++ b/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" -- GitLab