diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js index 72bc1ffbb..d0a621d6c 100644 --- a/app/actions/actionsTypes.js +++ b/app/actions/actionsTypes.js @@ -12,10 +12,12 @@ function createRequestTypes(base, types = defaultTypes) { export const LOGIN = createRequestTypes('LOGIN', [ ...defaultTypes, 'SET_TOKEN', + 'RESTORE_TOKEN', 'SUBMIT', 'REGISTER_SUBMIT', 'REGISTER_REQUEST', 'REGISTER_SUCCESS', + 'REGISTER_INCOMPLETE', 'SET_USERNAME_SUBMIT', 'SET_USERNAME_REQUEST', 'SET_USERNAME_SUCCESS' @@ -38,7 +40,13 @@ export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [ 'RESET' ]); 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 LOGOUT = 'LOGOUT'; // logout is always success diff --git a/app/actions/login.js b/app/actions/login.js index 4a67acf84..c603f42bd 100644 --- a/app/actions/login.js +++ b/app/actions/login.js @@ -32,6 +32,11 @@ export function registerSuccess(credentials) { credentials }; } +export function registerIncomplete() { + return { + type: types.LOGIN.REGISTER_INCOMPLETE + }; +} export function setUsernameSubmit(credentials) { return { @@ -76,6 +81,13 @@ export function setToken(user = {}) { }; } +export function restoreToken(token) { + return { + type: types.LOGIN.RESTORE_TOKEN, + token + }; +} + export function logout() { return { type: types.LOGOUT diff --git a/app/actions/server.js b/app/actions/server.js index cfc46ad89..b334981b5 100644 --- a/app/actions/server.js +++ b/app/actions/server.js @@ -41,3 +41,9 @@ export function changedServer(server) { server }; } + +export function gotoAddServer() { + return { + type: SERVER.GOTO_ADD + }; +} diff --git a/app/containers/Routes.js b/app/containers/Routes.js index 83268096c..e1e57e950 100644 --- a/app/containers/Routes.js +++ b/app/containers/Routes.js @@ -7,6 +7,7 @@ import { appInit } from '../actions'; import AuthRoutes from './routes/AuthRoutes'; import PublicRoutes from './routes/PublicRoutes'; import Loading from '../presentation/Loading'; +import * as NavigationService from './routes/NavigationService'; @connect( state => ({ @@ -27,6 +28,11 @@ export default class Routes extends React.Component { componentWillMount() { this.props.appInit(); } + + componentDidUpdate() { + NavigationService.setNavigator(this.navigator); + } + render() { 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) { - return (); + return ( this.navigator = nav} />); } - return (); + return ( this.navigator = nav} />); } } diff --git a/app/containers/Sidebar.js b/app/containers/Sidebar.js index 46ece3c41..4d9f7eb75 100644 --- a/app/containers/Sidebar.js +++ b/app/containers/Sidebar.js @@ -5,7 +5,7 @@ import { DrawerItems } from 'react-navigation'; import { connect } from 'react-redux'; import realm from '../lib/realm'; -import { setServer } from '../actions/server'; +import { setServer, gotoAddServer } from '../actions/server'; import { logout } from '../actions/login'; const styles = StyleSheet.create({ @@ -41,14 +41,16 @@ const styles = StyleSheet.create({ server: state.server.server }), dispatch => ({ selectServer: server => dispatch(setServer(server)), - logout: () => dispatch(logout()) + logout: () => dispatch(logout()), + gotoAddServer: () => dispatch(gotoAddServer()) })) export default class Sidebar extends Component { static propTypes = { server: PropTypes.string.isRequired, selectServer: PropTypes.func.isRequired, navigation: PropTypes.object.isRequired, - logout: PropTypes.func.isRequired + logout: PropTypes.func.isRequired, + gotoAddServer: PropTypes.func.isRequired } componentWillMount() { @@ -117,6 +119,15 @@ export default class Sidebar extends Component { + { this.props.gotoAddServer(); }} + > + + + Add Server + + + ); diff --git a/app/containers/routes/NavigationService.js b/app/containers/routes/NavigationService.js new file mode 100644 index 000000000..0be7f410b --- /dev/null +++ b/app/containers/routes/NavigationService.js @@ -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); + } +} diff --git a/app/containers/routes/PublicRoutes.js b/app/containers/routes/PublicRoutes.js index 92781c00d..41c267173 100644 --- a/app/containers/routes/PublicRoutes.js +++ b/app/containers/routes/PublicRoutes.js @@ -7,6 +7,9 @@ import ListServerView from '../../views/ListServerView'; import NewServerView from '../../views/NewServerView'; import LoginView from '../../views/LoginView'; import RegisterView from '../../views/RegisterView'; + +import TermsServiceView from '../../views/TermsServiceView'; +import PrivacyPolicyView from '../../views/PrivacyPolicyView'; import ForgotPasswordView from '../../views/ForgotPasswordView'; const PublicRoutes = StackNavigator( @@ -45,6 +48,18 @@ const PublicRoutes = StackNavigator( title: 'Register' } }, + TermsService: { + screen: TermsServiceView, + navigationOptions: { + title: 'Terms of service' + } + }, + PrivacyPolicy: { + screen: PrivacyPolicyView, + navigationOptions: { + title: 'Privacy policy' + } + }, ForgotPassword: { screen: ForgotPasswordView, navigationOptions: { diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 81bc55f40..22585a6ff 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -23,6 +23,8 @@ const call = (method, ...params) => new Promise((resolve, reject) => { const TOKEN_KEY = 'reactnativemeteor_usertoken'; const RocketChat = { + TOKEN_KEY, + createChannel({ name, users, type }) { return new Promise((resolve, reject) => { 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 }) { return new Promise((resolve, reject) => { Meteor.call('registerUser', credentials, (err, userId) => { @@ -438,6 +451,7 @@ const RocketChat = { }, logout({ server }) { Meteor.logout(); + Meteor.disconnect(); AsyncStorage.removeItem(TOKEN_KEY); AsyncStorage.removeItem(`${ TOKEN_KEY }-${ server }`); } diff --git a/app/reducers/login.js b/app/reducers/login.js index 81ff98c3c..e90531ffd 100644 --- a/app/reducers/login.js +++ b/app/reducers/login.js @@ -45,6 +45,11 @@ export default function login(state = initialState, action) { token: action.token, user: action.user }; + case types.LOGIN.RESTORE_TOKEN: + return { + ...state, + token: action.token + }; case types.LOGIN.REGISTER_SUBMIT: return { ...state, @@ -73,6 +78,11 @@ export default function login(state = initialState, action) { isFetching: false, isRegistering: false }; + case types.LOGIN.REGISTER_INCOMPLETE: + return { + ...state, + isRegistering: true + }; case types.FORGOT_PASSWORD.INIT: return initialState; case types.FORGOT_PASSWORD.REQUEST: diff --git a/app/sagas/init.js b/app/sagas/init.js index cb53af7de..c86b5caad 100644 --- a/app/sagas/init.js +++ b/app/sagas/init.js @@ -2,16 +2,23 @@ import { AsyncStorage } from 'react-native'; import { call, put, take } from 'redux-saga/effects'; import * as actions from '../actions'; import { setServer } from '../actions/server'; +import { restoreToken } from '../actions/login'; import { APP } from '../actions/actionsTypes'; const restore = function* restore() { try { 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'); - yield put(actions.appReady({})); if (currentServer) { yield put(setServer(currentServer)); } + + yield put(actions.appReady({})); } catch (e) { console.log(e); } diff --git a/app/sagas/login.js b/app/sagas/login.js index c16e9f1b4..bd9b5287a 100644 --- a/app/sagas/login.js +++ b/app/sagas/login.js @@ -5,6 +5,7 @@ import { loginRequest, loginSubmit, registerRequest, + registerIncomplete, loginSuccess, loginFailure, setToken, @@ -16,23 +17,24 @@ import { forgotPasswordFailure } from '../actions/login'; import RocketChat from '../lib/rocketchat'; +import * as NavigationService from '../containers/routes/NavigationService'; -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 logoutCall = args => RocketChat.logout(args); +const meCall = args => RocketChat.me(args); const forgotPasswordCall = args => RocketChat.forgotPassword(args); const getToken = function* getToken() { 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) { try { 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); } catch (e) { console.log('getTokenerr', e); @@ -43,47 +45,41 @@ const getToken = function* getToken() { }; const handleLoginWhenServerChanges = function* handleLoginWhenServerChanges() { - // do { try { yield take(types.METEOR.SUCCESS); yield call(getToken); - // const { navigator } = yield select(state => state); const user = yield select(getUser); if (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) { console.log(e); } - // } while (true); }; const saveToken = function* saveToken() { const [server, user] = yield all([select(getServer), select(getUser)]); - yield AsyncStorage.setItem(TOKEN_KEY, user.token); - yield AsyncStorage.setItem(`${ TOKEN_KEY }-${ server }`, JSON.stringify(user)); + yield AsyncStorage.setItem(RocketChat.TOKEN_KEY, user.token); + yield AsyncStorage.setItem(`${ RocketChat.TOKEN_KEY }-${ server }`, JSON.stringify(user)); }; const handleLoginRequest = function* handleLoginRequest({ credentials }) { try { - const response = yield call(loginCall, credentials); - yield put(loginSuccess(response)); + const server = yield select(getServer); + 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) { if (err.error === 403) { yield put(logout()); @@ -98,13 +94,7 @@ const handleLoginSubmit = function* handleLoginSubmit({ credentials }) { }; const handleRegisterSubmit = function* handleRegisterSubmit({ credentials }) { - // put a login request 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 }) { @@ -141,6 +131,10 @@ const handleLogout = function* handleLogout() { yield call(logoutCall, { server }); }; +const handleRegisterIncomplete = function* handleRegisterIncomplete() { + yield call(NavigationService.navigate, 'Register'); +}; + const handleForgotPasswordRequest = function* handleForgotPasswordRequest({ email }) { try { yield call(forgotPasswordCall, email); @@ -158,6 +152,7 @@ const root = function* root() { 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.REGISTER_INCOMPLETE, handleRegisterIncomplete); yield takeLatest(types.LOGIN.SET_USERNAME_SUBMIT, handleSetUsernameSubmit); yield takeLatest(types.LOGIN.SET_USERNAME_REQUEST, handleSetUsernameRequest); yield takeLatest(types.LOGOUT, handleLogout); diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js index dc9b039ec..84ad3a3ee 100644 --- a/app/sagas/selectServer.js +++ b/app/sagas/selectServer.js @@ -1,12 +1,13 @@ import { put, takeEvery, call, takeLatest, race, take } from 'redux-saga/effects'; import { delay } from 'redux-saga'; import { AsyncStorage } from 'react-native'; -// import { Navigation } from 'react-native-navigation'; import { SERVER } from '../actions/actionsTypes'; 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 realm from '../lib/realm'; +import * as NavigationService from '../containers/routes/NavigationService'; const validate = function* validate(server) { return yield RocketChat.testServer(server); @@ -42,13 +43,21 @@ const addServer = function* addServer({ server }) { realm.write(() => { 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() { yield takeLatest(SERVER.REQUEST, validateServer); yield takeEvery(SERVER.SELECT, selectServer); yield takeEvery(SERVER.ADD, addServer); + yield takeEvery(SERVER.GOTO_ADD, handleGotoAddServer); }; export default root; diff --git a/app/views/LoginView.js b/app/views/LoginView.js index a5e1ad923..0b508eddf 100644 --- a/app/views/LoginView.js +++ b/app/views/LoginView.js @@ -1,6 +1,6 @@ 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 { Keyboard, Text, TextInput, View, TouchableOpacity, SafeAreaView } from 'react-native'; @@ -49,6 +49,14 @@ class LoginView extends React.Component { this.props.navigation.navigate('Register'); } + termsService = () => { + this.props.navigation.navigate('TermsService'); + } + + privacyPolicy = () => { + this.props.navigation.navigate('PrivacyPolicy'); + } + forgotPassword = () => { this.props.navigation.navigate('ForgotPassword'); } @@ -78,8 +86,8 @@ class LoginView extends React.Component { contentContainerStyle={styles.container} keyboardVerticalOffset={128} > - - + + - LOGIN + + LOGIN - - REGISTER + + REGISTER + + TERMS OF SERVICE + + + + PRIVACY POLICY + FORGOT MY PASSWORD {this.props.login.failure && {this.props.login.error.reason}} - - - + + ); } diff --git a/app/views/PrivacyPolicyView.js b/app/views/PrivacyPolicyView.js new file mode 100644 index 000000000..e4f96abc1 --- /dev/null +++ b/app/views/PrivacyPolicyView.js @@ -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 ( + + ); + } +} + +function mapStateToProps(state) { + return { + privacyPolicy: state.settings.Layout_Privacy_Policy + }; +} + +export default connect(mapStateToProps)(PrivacyPolicyView); diff --git a/app/views/RegisterView.js b/app/views/RegisterView.js index 5ddd96682..ed2625869 100644 --- a/app/views/RegisterView.js +++ b/app/views/RegisterView.js @@ -125,12 +125,14 @@ class RegisterView extends React.Component { placeholder={this.props.Accounts_RepeatPasswordPlaceholder || 'Repeat Password'} /> - + REGISTER @@ -158,12 +160,11 @@ class RegisterView extends React.Component { placeholder={this.props.Accounts_UsernamePlaceholder || 'Username'} /> - - REGISTER - + + REGISTER {this.props.login.failure && {this.props.login.error.reason}} diff --git a/app/views/TermsServiceView.js b/app/views/TermsServiceView.js new file mode 100644 index 000000000..b56abca08 --- /dev/null +++ b/app/views/TermsServiceView.js @@ -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 ( + + ); + } +} + +function mapStateToProps(state) { + return { + termsService: state.settings.Layout_Terms_of_Service + }; +} + +export default connect(mapStateToProps)(TermsServiceView);