Merge remote-tracking branch 'origin/develop' into update

# Conflicts:
#	app/views/LoginView.js
#	app/views/RegisterView.js
This commit is contained in:
Rodrigo Nascimento 2017-11-13 12:56:57 -02:00
commit 6c4cef143d
No known key found for this signature in database
GPG Key ID: CFCE33B7B01AC335
16 changed files with 248 additions and 58 deletions

View File

@ -12,10 +12,12 @@ function createRequestTypes(base, types = defaultTypes) {
export const LOGIN = createRequestTypes('LOGIN', [ export const LOGIN = createRequestTypes('LOGIN', [
...defaultTypes, ...defaultTypes,
'SET_TOKEN', 'SET_TOKEN',
'RESTORE_TOKEN',
'SUBMIT', 'SUBMIT',
'REGISTER_SUBMIT', 'REGISTER_SUBMIT',
'REGISTER_REQUEST', 'REGISTER_REQUEST',
'REGISTER_SUCCESS', 'REGISTER_SUCCESS',
'REGISTER_INCOMPLETE',
'SET_USERNAME_SUBMIT', 'SET_USERNAME_SUBMIT',
'SET_USERNAME_REQUEST', 'SET_USERNAME_REQUEST',
'SET_USERNAME_SUCCESS' 'SET_USERNAME_SUCCESS'
@ -38,7 +40,13 @@ export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [
'RESET' 'RESET'
]); ]);
export const NAVIGATION = createRequestTypes('NAVIGATION', ['SET']); export const NAVIGATION = createRequestTypes('NAVIGATION', ['SET']);
export const SERVER = createRequestTypes('SERVER', [...defaultTypes, 'SELECT', 'CHANGED', 'ADD']); export const SERVER = createRequestTypes('SERVER', [
...defaultTypes,
'SELECT',
'CHANGED',
'ADD',
'GOTO_ADD'
]);
export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT']); export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT']);
export const LOGOUT = 'LOGOUT'; // logout is always success export const LOGOUT = 'LOGOUT'; // logout is always success

View File

@ -32,6 +32,11 @@ export function registerSuccess(credentials) {
credentials credentials
}; };
} }
export function registerIncomplete() {
return {
type: types.LOGIN.REGISTER_INCOMPLETE
};
}
export function setUsernameSubmit(credentials) { export function setUsernameSubmit(credentials) {
return { return {
@ -76,6 +81,13 @@ export function setToken(user = {}) {
}; };
} }
export function restoreToken(token) {
return {
type: types.LOGIN.RESTORE_TOKEN,
token
};
}
export function logout() { export function logout() {
return { return {
type: types.LOGOUT type: types.LOGOUT

View File

@ -41,3 +41,9 @@ export function changedServer(server) {
server server
}; };
} }
export function gotoAddServer() {
return {
type: SERVER.GOTO_ADD
};
}

View File

@ -7,6 +7,7 @@ import { appInit } from '../actions';
import AuthRoutes from './routes/AuthRoutes'; import AuthRoutes from './routes/AuthRoutes';
import PublicRoutes from './routes/PublicRoutes'; import PublicRoutes from './routes/PublicRoutes';
import Loading from '../presentation/Loading'; import Loading from '../presentation/Loading';
import * as NavigationService from './routes/NavigationService';
@connect( @connect(
state => ({ state => ({
@ -27,6 +28,11 @@ export default class Routes extends React.Component {
componentWillMount() { componentWillMount() {
this.props.appInit(); this.props.appInit();
} }
componentDidUpdate() {
NavigationService.setNavigator(this.navigator);
}
render() { render() {
const { login, app } = this.props; const { login, app } = this.props;
@ -35,9 +41,9 @@ export default class Routes extends React.Component {
} }
if ((login.token && !login.failure && !login.isRegistering) || app.ready) { if ((login.token && !login.failure && !login.isRegistering) || app.ready) {
return (<AuthRoutes />); return (<AuthRoutes ref={nav => this.navigator = nav} />);
} }
return (<PublicRoutes />); return (<PublicRoutes ref={nav => this.navigator = nav} />);
} }
} }

View File

@ -5,7 +5,7 @@ import { DrawerItems } from 'react-navigation';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import realm from '../lib/realm'; import realm from '../lib/realm';
import { setServer } from '../actions/server'; import { setServer, gotoAddServer } from '../actions/server';
import { logout } from '../actions/login'; import { logout } from '../actions/login';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -41,14 +41,16 @@ const styles = StyleSheet.create({
server: state.server.server server: state.server.server
}), dispatch => ({ }), dispatch => ({
selectServer: server => dispatch(setServer(server)), selectServer: server => dispatch(setServer(server)),
logout: () => dispatch(logout()) logout: () => dispatch(logout()),
gotoAddServer: () => dispatch(gotoAddServer())
})) }))
export default class Sidebar extends Component { export default class Sidebar extends Component {
static propTypes = { static propTypes = {
server: PropTypes.string.isRequired, server: PropTypes.string.isRequired,
selectServer: PropTypes.func.isRequired, selectServer: PropTypes.func.isRequired,
navigation: PropTypes.object.isRequired, navigation: PropTypes.object.isRequired,
logout: PropTypes.func.isRequired logout: PropTypes.func.isRequired,
gotoAddServer: PropTypes.func.isRequired
} }
componentWillMount() { componentWillMount() {
@ -117,6 +119,15 @@ export default class Sidebar extends Component {
</Text> </Text>
</View> </View>
</TouchableHighlight> </TouchableHighlight>
<TouchableHighlight
onPress={() => { this.props.gotoAddServer(); }}
>
<View style={styles.serverItem}>
<Text>
Add Server
</Text>
</View>
</TouchableHighlight>
</View> </View>
</ScrollView> </ScrollView>
); );

View File

@ -0,0 +1,23 @@
import { NavigationActions } from 'react-navigation';
const config = {};
export function setNavigator(nav) {
if (nav) {
config.navigator = nav;
}
}
export function navigate(routeName, params) {
if (config.navigator && routeName) {
const action = NavigationActions.navigate({ routeName, params });
config.navigator.dispatch(action);
}
}
export function goBack() {
if (config.navigator) {
const action = NavigationActions.back({});
config.navigator.dispatch(action);
}
}

View File

@ -7,6 +7,9 @@ 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 TermsServiceView from '../../views/TermsServiceView';
import PrivacyPolicyView from '../../views/PrivacyPolicyView';
import ForgotPasswordView from '../../views/ForgotPasswordView'; import ForgotPasswordView from '../../views/ForgotPasswordView';
const PublicRoutes = StackNavigator( const PublicRoutes = StackNavigator(
@ -45,6 +48,18 @@ const PublicRoutes = StackNavigator(
title: 'Register' title: 'Register'
} }
}, },
TermsService: {
screen: TermsServiceView,
navigationOptions: {
title: 'Terms of service'
}
},
PrivacyPolicy: {
screen: PrivacyPolicyView,
navigationOptions: {
title: 'Privacy policy'
}
},
ForgotPassword: { ForgotPassword: {
screen: ForgotPasswordView, screen: ForgotPasswordView,
navigationOptions: { navigationOptions: {

View File

@ -23,6 +23,8 @@ const call = (method, ...params) => new Promise((resolve, reject) => {
const TOKEN_KEY = 'reactnativemeteor_usertoken'; const TOKEN_KEY = 'reactnativemeteor_usertoken';
const RocketChat = { const RocketChat = {
TOKEN_KEY,
createChannel({ name, users, type }) { createChannel({ name, users, type }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
Meteor.call(type ? 'createChannel' : 'createPrivateGroup', name, users, type, (err, res) => (err ? reject(err) : resolve(res))); Meteor.call(type ? 'createChannel' : 'createPrivateGroup', name, users, type, (err, res) => (err ? reject(err) : resolve(res)));
@ -122,6 +124,17 @@ const RocketChat = {
}); });
}, },
me({ server, token, userId }) {
return fetch(`${ server }/api/v1/me`, {
method: 'get',
headers: {
'Content-Type': 'application/json',
'X-Auth-Token': token,
'X-User-Id': userId
}
}).then(response => response.json());
},
register({ credentials }) { register({ credentials }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
Meteor.call('registerUser', credentials, (err, userId) => { Meteor.call('registerUser', credentials, (err, userId) => {
@ -438,6 +451,7 @@ const RocketChat = {
}, },
logout({ server }) { logout({ server }) {
Meteor.logout(); Meteor.logout();
Meteor.disconnect();
AsyncStorage.removeItem(TOKEN_KEY); AsyncStorage.removeItem(TOKEN_KEY);
AsyncStorage.removeItem(`${ TOKEN_KEY }-${ server }`); AsyncStorage.removeItem(`${ TOKEN_KEY }-${ server }`);
} }

View File

@ -45,6 +45,11 @@ export default function login(state = initialState, action) {
token: action.token, token: action.token,
user: action.user user: action.user
}; };
case types.LOGIN.RESTORE_TOKEN:
return {
...state,
token: action.token
};
case types.LOGIN.REGISTER_SUBMIT: case types.LOGIN.REGISTER_SUBMIT:
return { return {
...state, ...state,
@ -73,6 +78,11 @@ export default function login(state = initialState, action) {
isFetching: false, isFetching: false,
isRegistering: false isRegistering: false
}; };
case types.LOGIN.REGISTER_INCOMPLETE:
return {
...state,
isRegistering: true
};
case types.FORGOT_PASSWORD.INIT: case types.FORGOT_PASSWORD.INIT:
return initialState; return initialState;
case types.FORGOT_PASSWORD.REQUEST: case types.FORGOT_PASSWORD.REQUEST:

View File

@ -2,16 +2,23 @@ import { AsyncStorage } from 'react-native';
import { call, put, take } from 'redux-saga/effects'; import { call, put, take } from 'redux-saga/effects';
import * as actions from '../actions'; import * as actions from '../actions';
import { setServer } from '../actions/server'; import { setServer } from '../actions/server';
import { restoreToken } from '../actions/login';
import { APP } from '../actions/actionsTypes'; import { APP } from '../actions/actionsTypes';
const restore = function* restore() { const restore = function* restore() {
try { try {
yield take(APP.INIT); yield take(APP.INIT);
const token = yield call([AsyncStorage, 'getItem'], 'reactnativemeteor_usertoken');
if (token) {
yield put(restoreToken(token));
}
const currentServer = yield call([AsyncStorage, 'getItem'], 'currentServer'); const currentServer = yield call([AsyncStorage, 'getItem'], 'currentServer');
yield put(actions.appReady({}));
if (currentServer) { if (currentServer) {
yield put(setServer(currentServer)); yield put(setServer(currentServer));
} }
yield put(actions.appReady({}));
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }

View File

@ -5,6 +5,7 @@ import {
loginRequest, loginRequest,
loginSubmit, loginSubmit,
registerRequest, registerRequest,
registerIncomplete,
loginSuccess, loginSuccess,
loginFailure, loginFailure,
setToken, setToken,
@ -16,23 +17,24 @@ import {
forgotPasswordFailure forgotPasswordFailure
} from '../actions/login'; } from '../actions/login';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import * as NavigationService from '../containers/routes/NavigationService';
const TOKEN_KEY = 'reactnativemeteor_usertoken';
const getUser = state => state.login; const getUser = state => state.login;
const getServer = state => state.server.server; const getServer = state => state.server.server;
const loginCall = args => (args.resume ? RocketChat.login(args) : RocketChat.loginWithPassword(args)); const loginCall = args => (args.resume ? RocketChat.login(args) : RocketChat.loginWithPassword(args));
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 meCall = args => RocketChat.me(args);
const forgotPasswordCall = args => RocketChat.forgotPassword(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);
const user = yield call([AsyncStorage, 'getItem'], `${ TOKEN_KEY }-${ currentServer }`); const user = yield call([AsyncStorage, 'getItem'], `${ RocketChat.TOKEN_KEY }-${ currentServer }`);
if (user) { if (user) {
try { try {
yield put(setToken(JSON.parse(user))); yield put(setToken(JSON.parse(user)));
yield call([AsyncStorage, 'setItem'], TOKEN_KEY, JSON.parse(user).token || ''); yield call([AsyncStorage, 'setItem'], RocketChat.TOKEN_KEY, JSON.parse(user).token || '');
return JSON.parse(user); return JSON.parse(user);
} catch (e) { } catch (e) {
console.log('getTokenerr', e); console.log('getTokenerr', e);
@ -43,47 +45,41 @@ const getToken = function* getToken() {
}; };
const handleLoginWhenServerChanges = function* handleLoginWhenServerChanges() { const handleLoginWhenServerChanges = function* handleLoginWhenServerChanges() {
// do {
try { try {
yield take(types.METEOR.SUCCESS); yield take(types.METEOR.SUCCESS);
yield call(getToken); yield call(getToken);
// const { navigator } = yield select(state => state);
const user = yield select(getUser); const user = yield select(getUser);
if (user.token) { if (user.token) {
yield put(loginRequest({ resume: user.token })); yield put(loginRequest({ resume: user.token }));
// console.log('AEEEEEEEEOOOOO');
// // wait for a response
// const { error } = yield race({
// success: take(types.LOGIN.SUCCESS),
// error: take(types.LOGIN.FAILURE)
// });
// console.log('AEEEEEEEEOOOOO', error);
// if (!error) {
// navigator.resetTo({
// screen: 'Rooms'
// });
// }
} }
// navigator.resetTo({
// screen: 'Rooms'
// });
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
// } while (true);
}; };
const saveToken = function* saveToken() { const saveToken = function* saveToken() {
const [server, user] = yield all([select(getServer), select(getUser)]); const [server, user] = yield all([select(getServer), select(getUser)]);
yield AsyncStorage.setItem(TOKEN_KEY, user.token); yield AsyncStorage.setItem(RocketChat.TOKEN_KEY, user.token);
yield AsyncStorage.setItem(`${ TOKEN_KEY }-${ server }`, JSON.stringify(user)); yield AsyncStorage.setItem(`${ RocketChat.TOKEN_KEY }-${ server }`, JSON.stringify(user));
}; };
const handleLoginRequest = function* handleLoginRequest({ credentials }) { const handleLoginRequest = function* handleLoginRequest({ credentials }) {
try { try {
const response = yield call(loginCall, credentials); const server = yield select(getServer);
yield put(loginSuccess(response)); const user = yield call(loginCall, credentials);
// GET /me from REST API
const me = yield call(meCall, { server, token: user.token, userId: user.id });
// if user has username
if (me.username) {
user.username = me.username;
} else {
yield put(registerIncomplete());
}
yield put(loginSuccess(user));
} catch (err) { } catch (err) {
if (err.error === 403) { if (err.error === 403) {
yield put(logout()); yield put(logout());
@ -98,13 +94,7 @@ const handleLoginSubmit = function* handleLoginSubmit({ credentials }) {
}; };
const handleRegisterSubmit = function* handleRegisterSubmit({ credentials }) { const handleRegisterSubmit = function* handleRegisterSubmit({ credentials }) {
// put a login request
yield put(registerRequest(credentials)); yield put(registerRequest(credentials));
// wait for a response
// yield race({
// success: take(types.LOGIN.REGISTER_SUCCESS),
// error: take(types.LOGIN.FAILURE)
// });
}; };
const handleRegisterRequest = function* handleRegisterRequest({ credentials }) { const handleRegisterRequest = function* handleRegisterRequest({ credentials }) {
@ -141,6 +131,10 @@ const handleLogout = function* handleLogout() {
yield call(logoutCall, { server }); yield call(logoutCall, { server });
}; };
const handleRegisterIncomplete = function* handleRegisterIncomplete() {
yield call(NavigationService.navigate, 'Register');
};
const handleForgotPasswordRequest = function* handleForgotPasswordRequest({ email }) { const handleForgotPasswordRequest = function* handleForgotPasswordRequest({ email }) {
try { try {
yield call(forgotPasswordCall, email); yield call(forgotPasswordCall, email);
@ -158,6 +152,7 @@ const root = function* root() {
yield takeLatest(types.LOGIN.REGISTER_REQUEST, handleRegisterRequest); yield takeLatest(types.LOGIN.REGISTER_REQUEST, handleRegisterRequest);
yield takeLatest(types.LOGIN.REGISTER_SUBMIT, handleRegisterSubmit); yield takeLatest(types.LOGIN.REGISTER_SUBMIT, handleRegisterSubmit);
yield takeLatest(types.LOGIN.REGISTER_SUCCESS, handleRegisterSuccess); yield takeLatest(types.LOGIN.REGISTER_SUCCESS, handleRegisterSuccess);
yield takeLatest(types.LOGIN.REGISTER_INCOMPLETE, handleRegisterIncomplete);
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);

View File

@ -1,12 +1,13 @@
import { put, takeEvery, call, takeLatest, race, take } from 'redux-saga/effects'; import { put, takeEvery, call, takeLatest, race, take } from 'redux-saga/effects';
import { delay } from 'redux-saga'; import { delay } from 'redux-saga';
import { AsyncStorage } from 'react-native'; import { AsyncStorage } from 'react-native';
// import { Navigation } from 'react-native-navigation';
import { SERVER } from '../actions/actionsTypes'; import { SERVER } from '../actions/actionsTypes';
import { connectRequest, disconnect } from '../actions/connect'; import { connectRequest, disconnect } from '../actions/connect';
import { changedServer, serverSuccess, serverFailure, serverRequest } from '../actions/server'; import { changedServer, serverSuccess, serverFailure, serverRequest, setServer } from '../actions/server';
import { logout } from '../actions/login';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import realm from '../lib/realm'; import realm from '../lib/realm';
import * as NavigationService from '../containers/routes/NavigationService';
const validate = function* validate(server) { const validate = function* validate(server) {
return yield RocketChat.testServer(server); return yield RocketChat.testServer(server);
@ -42,13 +43,21 @@ const addServer = function* addServer({ server }) {
realm.write(() => { realm.write(() => {
realm.create('servers', { id: server, current: false }, true); realm.create('servers', { id: server, current: false }, true);
}); });
yield put(setServer(server));
} }
}; };
const handleGotoAddServer = function* handleGotoAddServer() {
yield put(logout());
yield call(AsyncStorage.removeItem, RocketChat.TOKEN_KEY);
yield delay(1000);
yield call(NavigationService.navigate, 'AddServer');
};
const root = function* root() { const root = function* root() {
yield takeLatest(SERVER.REQUEST, validateServer); yield takeLatest(SERVER.REQUEST, validateServer);
yield takeEvery(SERVER.SELECT, selectServer); yield takeEvery(SERVER.SELECT, selectServer);
yield takeEvery(SERVER.ADD, addServer); yield takeEvery(SERVER.ADD, addServer);
yield takeEvery(SERVER.GOTO_ADD, handleGotoAddServer);
}; };
export default root; export default root;

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import Spinner from 'react-native-loading-spinner-overlay'; // import Spinner from 'react-native-loading-spinner-overlay';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Keyboard, Text, TextInput, View, TouchableOpacity, SafeAreaView } from 'react-native'; import { Keyboard, Text, TextInput, View, TouchableOpacity, SafeAreaView } from 'react-native';
@ -49,6 +49,14 @@ class LoginView extends React.Component {
this.props.navigation.navigate('Register'); this.props.navigation.navigate('Register');
} }
termsService = () => {
this.props.navigation.navigate('TermsService');
}
privacyPolicy = () => {
this.props.navigation.navigate('PrivacyPolicy');
}
forgotPassword = () => { forgotPassword = () => {
this.props.navigation.navigate('ForgotPassword'); this.props.navigation.navigate('ForgotPassword');
} }
@ -78,8 +86,8 @@ class LoginView extends React.Component {
contentContainerStyle={styles.container} contentContainerStyle={styles.container}
keyboardVerticalOffset={128} keyboardVerticalOffset={128}
> >
<SafeAreaView>
<View style={styles.loginView}> <View style={styles.loginView}>
<SafeAreaView>
<View style={styles.formContainer}> <View style={styles.formContainer}>
<TextInput <TextInput
style={styles.input_white} style={styles.input_white}
@ -107,23 +115,32 @@ class LoginView extends React.Component {
{this.renderTOTP()} {this.renderTOTP()}
<TouchableOpacity style={styles.buttonContainer}> <TouchableOpacity
<Text style={styles.button} onPress={this.submit}>LOGIN</Text> style={styles.buttonContainer}
onPress={this.submit}
>
<Text style={styles.button}>LOGIN</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity style={styles.buttonContainer}> <TouchableOpacity style={styles.buttonContainer} onPress={this.register}>
<Text style={styles.button} onPress={this.register}>REGISTER</Text> <Text style={styles.button}>REGISTER</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity style={styles.buttonContainer} onPress={this.termsService}>
<Text style={styles.button}>TERMS OF SERVICE</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.buttonContainer} onPress={this.privacyPolicy}>
<Text style={styles.button}>PRIVACY POLICY</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.buttonContainer} onPress={this.forgotPassword}> <TouchableOpacity style={styles.buttonContainer} onPress={this.forgotPassword}>
<Text style={styles.button}>FORGOT MY PASSWORD</Text> <Text style={styles.button}>FORGOT MY PASSWORD</Text>
</TouchableOpacity> </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' }} />
</View>
</SafeAreaView> </SafeAreaView>
</View>
</KeyboardView> </KeyboardView>
); );
} }

View File

@ -0,0 +1,28 @@
import React from 'react';
import PropTypes from 'prop-types';
import { WebView } from 'react-native';
import { connect } from 'react-redux';
class PrivacyPolicyView extends React.Component {
static propTypes = {
privacyPolicy: PropTypes.string
}
static navigationOptions = () => ({
title: 'Terms of service'
});
render() {
return (
<WebView source={{ html: this.props.privacyPolicy }} />
);
}
}
function mapStateToProps(state) {
return {
privacyPolicy: state.settings.Layout_Privacy_Policy
};
}
export default connect(mapStateToProps)(PrivacyPolicyView);

View File

@ -125,12 +125,14 @@ class RegisterView extends React.Component {
placeholder={this.props.Accounts_RepeatPasswordPlaceholder || 'Repeat Password'} placeholder={this.props.Accounts_RepeatPasswordPlaceholder || 'Repeat Password'}
/> />
<TouchableOpacity style={[styles.buttonContainer, styles.registerContainer]}> <TouchableOpacity
style={[styles.buttonContainer, styles.registerContainer]}
onPress={this.submit}
>
<Text <Text
style={[styles.button, this._valid() ? {} style={[styles.button, this._valid() ? {}
: { color: placeholderTextColor } : { color: placeholderTextColor }
]} ]}
onPress={this.submit}
>REGISTER >REGISTER
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
@ -158,12 +160,11 @@ class RegisterView extends React.Component {
placeholder={this.props.Accounts_UsernamePlaceholder || 'Username'} placeholder={this.props.Accounts_UsernamePlaceholder || 'Username'}
/> />
<TouchableOpacity style={[styles.buttonContainer, styles.registerContainer]}> <TouchableOpacity
<Text style={[styles.buttonContainer, styles.registerContainer]}
style={styles.button}
onPress={this.usernameSubmit} onPress={this.usernameSubmit}
>REGISTER >
</Text> <Text style={styles.button}>REGISTER</Text>
</TouchableOpacity> </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 File

@ -0,0 +1,28 @@
import React from 'react';
import PropTypes from 'prop-types';
import { WebView } from 'react-native';
import { connect } from 'react-redux';
class TermsServiceView extends React.Component {
static propTypes = {
termsService: PropTypes.string
}
static navigationOptions = () => ({
title: 'Terms of service'
});
render() {
return (
<WebView source={{ html: this.props.termsService }} />
);
}
}
function mapStateToProps(state) {
return {
termsService: state.settings.Layout_Terms_of_Service
};
}
export default connect(mapStateToProps)(TermsServiceView);