diff --git a/src/actions/index.js b/src/actions/index.js index 01354ba..e46c7cb 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -3,6 +3,7 @@ import { CHANGE_USER_REQUESTED, CLEAR_USER_DATA_REQUESTED, CLEAR_USER_DATA_SUCCEEDED, + FETCH_CURRENT_USER_REQUESTED, LAST_USER_LOGGED_REQUESTED, USER_LOGIN_FAILED, USER_LOGIN_REQUESTED, @@ -19,6 +20,7 @@ export {ERROR_OCCURRED}; export {CHANGE_USER_REQUESTED}; export {CLEAR_USER_DATA_REQUESTED}; export {CLEAR_USER_DATA_SUCCEEDED}; +export {FETCH_CURRENT_USER_REQUESTED}; export {LAST_USER_LOGGED_REQUESTED}; export {USER_LOGIN_REQUESTED}; export {USER_LOGIN_SUCCEEDED}; diff --git a/src/actions/session.js b/src/actions/session.js index e5429b2..360fb2e 100755 --- a/src/actions/session.js +++ b/src/actions/session.js @@ -10,25 +10,16 @@ export const requestLogin = (user, authEndpoint, redirectUri, clientCredentials) clientCredentials }); -export const notifyLoginFail = () => ({ - type: USER_LOGIN_FAILED -}); +export const notifyLoginFail = () => ({type: USER_LOGIN_FAILED}); -export const notifyLoginSucceeded = () => ({ - type: USER_LOGIN_SUCCEEDED -}); +export const notifyLoginSucceeded = () => ({type: USER_LOGIN_SUCCEEDED}); export const USER_FETCH_TOKEN_REQUESTED = 'USER_FETCH_TOKEN_REQUESTED'; export const USER_FETCH_TOKEN_SUCCEEDED = 'USER_TOKEN_SUCCEEDED'; -export const requestFetchToken = () => ({ - type: USER_FETCH_TOKEN_REQUESTED -}); +export const requestFetchToken = () => ({type: USER_FETCH_TOKEN_REQUESTED}); -export const notifyFetchTokenSucceeded = token => ({ - type: USER_FETCH_TOKEN_SUCCEEDED, - token -}); +export const notifyFetchTokenSucceeded = token => ({type: USER_FETCH_TOKEN_SUCCEEDED, token}); export const USER_FETCH_REFRESH_TOKEN_REQUESTED = 'USER_FETCH_REFRESH_TOKEN_REQUESTED'; export const USER_FETCH_REFRESH_TOKEN_SUCCEEDED = 'USER_FETCH_REFRESH_TOKEN_SUCCEEDED'; @@ -40,9 +31,7 @@ export const requestFetchRefreshToken = (authEndpoint, clientId, clientSecret) = clientSecret }); -export const notifyFetchRefreshTokenSucceeded = () => ({ - type: USER_FETCH_REFRESH_TOKEN_SUCCEEDED -}); +export const notifyFetchRefreshTokenSucceeded = () => ({type: USER_FETCH_REFRESH_TOKEN_SUCCEEDED}); export const USER_REFRESH_ACCESS_TOKEN_REQUESTED = 'USER_REFRESH_ACCESS_TOKEN_REQUESTED'; export const USER_REFRESH_ACCESS_TOKEN_SUCCEEDED = 'USER_REFRESH_ACCESS_TOKEN_SUCCEEDED'; @@ -54,40 +43,26 @@ export const requestFetchRefreshAccessToken = (authEndpoint, clientId, clientSec clientSecret }); -export const notifyRefreshAccessToken = () => ({ - type: USER_REFRESH_ACCESS_TOKEN_SUCCEEDED -}); +export const notifyRefreshAccessToken = () => ({type: USER_REFRESH_ACCESS_TOKEN_SUCCEEDED}); export const LAST_USER_LOGGED_REQUESTED = 'LAST_USER_LOGGED_REQUESTED'; export const LAST_USER_LOGGED_SUCCEEDED = 'LAST_USER_LOGGED_SUCCEEDED'; -export const requestLastUserLogged = () => ({ - type: LAST_USER_LOGGED_REQUESTED -}); +export const requestLastUserLogged = () => ({type: LAST_USER_LOGGED_REQUESTED}); -export const receiveLastUserLogged = lastUserLogged => ({ - type: LAST_USER_LOGGED_SUCCEEDED, - lastUserLogged -}); +export const receiveLastUserLogged = lastUserLogged => ({type: LAST_USER_LOGGED_SUCCEEDED, lastUserLogged}); export const CLEAR_USER_DATA_REQUESTED = 'CLEAR_USER_DATA_REQUESTED'; export const CLEAR_USER_DATA_SUCCEEDED = 'CLEAR_USER_DATA_SUCCEEDED'; -export const requestClearUserData = () => ({ - type: CLEAR_USER_DATA_REQUESTED -}); +export const requestClearUserData = () => ({type: CLEAR_USER_DATA_REQUESTED}); -export const notifyDataCleared = () => ({ - type: CLEAR_USER_DATA_SUCCEEDED -}); +export const notifyDataCleared = () => ({type: CLEAR_USER_DATA_SUCCEEDED}); export const CHANGE_USER_REQUESTED = 'CHANGE_USER_REQUESTED'; export const CHANGE_USER_SUCCEEDED = 'CHANGE_USER_SUCCEEDED'; -export const requestChangeUser = userProfile => ({ - type: CHANGE_USER_REQUESTED, - userProfile -}); +export const requestChangeUser = userProfile => ({type: CHANGE_USER_REQUESTED, userProfile}); export const userChanged = () => ({ type: CHANGE_USER_SUCCEEDED @@ -107,3 +82,10 @@ export const CLEAN_USER_VALIDATIONS = 'CLEAN_USER_VALIDATIONS'; export const cleanUserValidations = () => ({ type: CLEAN_USER_VALIDATIONS }); + +export const FETCH_CURRENT_USER_REQUESTED = 'FETCH_CURRENT_USER_REQUESTED'; +export const FETCH_CURRENT_USER_SUCCEEDED = 'FETCH_CURRENT_USER_SUCCEEDED'; + +export const requestFetchCurrentUser = () => ({type: FETCH_CURRENT_USER_REQUESTED}); + +export const receiveCurrentUser = user => ({type: FETCH_CURRENT_USER_SUCCEEDED, user}); diff --git a/src/components/DrawerLayout/Drawer.js b/src/components/DrawerLayout/Drawer.js new file mode 100644 index 0000000..143bc16 --- /dev/null +++ b/src/components/DrawerLayout/Drawer.js @@ -0,0 +1,61 @@ +import React, {Fragment} from 'react'; +import PropTypes from 'prop-types'; +import {Divider} from 'react-native-elements'; +import {isEmpty} from 'lodash'; + +import stylePropType from '../../util/stylePropType'; +import routePropType from '../../util/routePropType'; + +import DrawerImage from './DrawerImage'; +import Routes from './Routes'; +import User from './User'; +import Version from './Version'; +import styles from './styles'; + +const brandImageDefault = require('../../images/brand.png'); + +const Drawer = ({ + routes, text, brandImage, style, rightImage, version, user +}) => ( + + + {user && } + + {!isEmpty(routes) && } + + {version && ( + + )} + +); + +Drawer.propTypes = { + brandImage: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string + ]), + rightImage: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string + ]), + style: stylePropType, + text: PropTypes.string, + version: PropTypes.string, + routes: PropTypes.arrayOf(routePropType), + user: PropTypes.shape({}) +}; + +Drawer.defaultProps = { + brandImage: brandImageDefault, + style: {}, + text: null, + rightImage: null, + version: null, + user: null, + routes: [] +}; + +export default Drawer; diff --git a/src/components/DrawerLayout/DrawerImage.js b/src/components/DrawerLayout/DrawerImage.js new file mode 100644 index 0000000..4c6a30b --- /dev/null +++ b/src/components/DrawerLayout/DrawerImage.js @@ -0,0 +1,51 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {Image, Text, View} from 'react-native'; + +import styles from './styles'; + +import stylePropType from '../../util/stylePropType'; + +const brandImageDefault = require('../../images/brand.png'); + +const DrawerImage = ({ + brandImage, rightImage, text, style +}) => ( + + + + + {text && ( + + {text} + + )} + {rightImage && ( + + + + )} + +); + +DrawerImage.propTypes = { + brandImage: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string + ]), + rightImage: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string + ]), + style: stylePropType, + text: PropTypes.string +}; + +DrawerImage.defaultProps = { + brandImage: brandImageDefault, + style: {}, + text: null, + rightImage: null +}; + +export default DrawerImage; diff --git a/src/components/DrawerLayout/NavItem.js b/src/components/DrawerLayout/NavItem.js new file mode 100644 index 0000000..af7bcd4 --- /dev/null +++ b/src/components/DrawerLayout/NavItem.js @@ -0,0 +1,43 @@ +import React, {PureComponent} from 'react'; +import PropTypes from 'prop-types'; +import {connect} from 'react-redux'; +import {Text, View} from 'react-native'; +import {Icon} from 'react-native-elements'; +import {Link} from 'react-router-native'; + +import {requestFetchToken} from '../../actions/session'; +import getFontAwesome from '../../util/getFontAwesome'; +import routePropType from '../../util/routePropType'; +import SessionService from '../../services/session'; + +import styles from './styles'; + +class NavItem extends PureComponent { + static propTypes = { + requestFetchToken: PropTypes.func.isRequired, + route: routePropType.isRequired + }; + + async signOut(closeSession) { + if (closeSession) { + await SessionService.clearSession(); + this.props.requestFetchToken(); + } + } + + render() { + const {route} = this.props; + return ( + + + this.signOut(route.closeSession)}> + + {route.text} + + + + ); + } +} + +export default NavItem; diff --git a/src/components/DrawerLayout/Routes.js b/src/components/DrawerLayout/Routes.js new file mode 100644 index 0000000..af414f7 --- /dev/null +++ b/src/components/DrawerLayout/Routes.js @@ -0,0 +1,28 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {View} from 'react-native'; + +import routePropType from '../../util/routePropType'; +import stylePropType from '../../util/stylePropType'; + +import NavItem from './NavItem'; +import styles from './styles'; + +const Routes = ({routes, style}) => ( + + {routes.map(route => ( + + ))} + +); + +Routes.propTypes = { + routes: PropTypes.arrayOf(routePropType).isRequired, + style: stylePropType +}; + +Routes.defaultProps = { + style: {} +}; + +export default Routes; diff --git a/src/components/DrawerLayout/User.js b/src/components/DrawerLayout/User.js new file mode 100644 index 0000000..c4ab154 --- /dev/null +++ b/src/components/DrawerLayout/User.js @@ -0,0 +1,27 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {Text, View} from 'react-native'; + +import stylePropType from '../../util/stylePropType'; + +import styles from './styles'; + +const User = ({user, style}) => ( + + + {`${user.name}, ${user.surname} (${user.username})`} + + +); + +User.propTypes = { + user: PropTypes.shape({}), + style: stylePropType +}; + +User.defaultProps = { + user: {}, + style: {} +}; + +export default User; diff --git a/src/components/DrawerLayout/Version.js b/src/components/DrawerLayout/Version.js new file mode 100644 index 0000000..a1c736e --- /dev/null +++ b/src/components/DrawerLayout/Version.js @@ -0,0 +1,27 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {Text, View} from 'react-native'; + +import stylePropType from '../../util/stylePropType'; + +import styles from './styles'; + +const Version = ({version, style}) => ( + + + {version} + + +); + +Version.propTypes = { + version: PropTypes.string, + style: stylePropType +}; + +Version.defaultProps = { + version: null, + style: {} +}; + +export default Version; diff --git a/src/components/DrawerLayout/index.js b/src/components/DrawerLayout/index.js new file mode 100755 index 0000000..2a3271e --- /dev/null +++ b/src/components/DrawerLayout/index.js @@ -0,0 +1,75 @@ +import React, {PureComponent} from 'react'; +import PropTypes from 'prop-types'; +import {connect} from 'react-redux'; +import {DrawerLayoutAndroid} from 'react-native'; + +import {requestFetchCurrentUser} from '../../actions/session'; +import stylePropType from '../../util/stylePropType'; +import routePropType from '../../util/routePropType'; + +import Drawer from './Drawer'; + +const brandImageDefault = require('../../images/brand.png'); + +class DrawerLayout extends PureComponent { + static propTypes = { + requestFetchCurrentUser: PropTypes.func.isRequired, + brandImage: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string + ]), + rightImage: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string + ]), + style: stylePropType, + text: PropTypes.string, + version: PropTypes.string, + routes: PropTypes.arrayOf(routePropType).isRequired, + token: PropTypes.string, + user: PropTypes.shape({}), + children: PropTypes.shape({}), + drawerRef: PropTypes.func + }; + + static defaultProps = { + brandImage: brandImageDefault, + style: {}, + user: {}, + token: null, + text: null, + rightImage: null, + version: null, + children: null, + drawerRef: null + }; + + componentDidMount() { + this.props.requestFetchCurrentUser(); + } + + render() { + const {children, drawerRef} = this.props; + return ( + } + ref={ref => drawerRef(ref)} + {...this.props} + > + {children} + + ); + } +} + +export default connect( + state => ({ + token: state.session.token, + user: state.session.user + }), + dispatch => ({ + requestFetchCurrentUser: () => dispatch(requestFetchCurrentUser()) + }) +)(DrawerLayout); diff --git a/src/components/DrawerLayout/index.web.js b/src/components/DrawerLayout/index.web.js new file mode 100644 index 0000000..93b6c77 --- /dev/null +++ b/src/components/DrawerLayout/index.web.js @@ -0,0 +1,55 @@ +import React, {Fragment} from 'react'; +import PropTypes from 'prop-types'; + +import stylePropType from '../../util/stylePropType'; +import routePropType from '../../util/routePropType'; +import Drawer from './Drawer'; + +const brandImageDefault = require('../../images/brand.png'); + +const DrawerLayout = ({ + routes, text, brandImage, style, rightImage, version, children +}) => ( + + + {children} + +); + +DrawerLayout.propTypes = { + brandImage: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string + ]), + rightImage: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string + ]), + style: stylePropType, + text: PropTypes.string, + routes: PropTypes.arrayOf(routePropType).isRequired, + version: PropTypes.string, + children: PropTypes.shape({}) +}; + +DrawerLayout.defaultProps = { + brandImage: brandImageDefault, + style: { + routesContainer: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'flex-start', + flex: 1, + backgroundColor: 'grey' + } + }, + text: null, + rightImage: null, + version: null, + children: {} +}; + +export default DrawerLayout; diff --git a/src/components/Header/styles.js b/src/components/DrawerLayout/styles.js similarity index 69% rename from src/components/Header/styles.js rename to src/components/DrawerLayout/styles.js index 13829bd..d8c42f9 100755 --- a/src/components/Header/styles.js +++ b/src/components/DrawerLayout/styles.js @@ -24,13 +24,7 @@ export default StyleSheet.create({ color: '#fff' }, navContainer: { - height: 45, - paddingRight: 10, - paddingLeft: 10, - flexDirection: 'row', - justifyContent: 'space-around', - alignItems: 'center', - backgroundColor: '#dee2e3' + flexDirection: 'row' }, navItem: { alignItems: 'center' @@ -51,5 +45,29 @@ export default StyleSheet.create({ flex: 1, flexDirection: 'column', alignItems: 'flex-end' + }, + userContainer: { + flex: 1, + justifyContent: 'center', + padding: 10 + }, + userText: { + fontSize: 16 + }, + versionContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'flex-end' + }, + versionText: { + fontSize: 16 + }, + routesContainer: { + flex: 1, + padding: 10, + justifyContent: 'space-around' + }, + dividerStyle: { + backgroundColor: '#dee2e3' } }); diff --git a/src/components/Header/NavItem.js b/src/components/Header/NavItem.js deleted file mode 100644 index 1664bf6..0000000 --- a/src/components/Header/NavItem.js +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import {Text} from 'react-native'; -import {Icon} from 'react-native-elements'; -import {Link} from 'react-router-native'; - -import getFontAwesome from '../../util/getFontAwesome'; -import routePropType from '../../util/routePropType'; -import Col from '../Col'; -import Row from '../Row'; -import styles from './styles'; - -const NavItem = ({onLogout, route}) => ( - - - - onLogout(route)}> - - {route.text} - - - - -); - -NavItem.propTypes = { - onLogout: PropTypes.func.isRequired, - route: routePropType.isRequired -}; - -export default NavItem; diff --git a/src/index.js b/src/index.js index 84bd45b..0d49a9d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,8 +1,8 @@ import Alert from './components/Alert'; import Button from './components/Button'; import Col from './components/Col'; +import DrawerLayout from './components/DrawerLayout'; import Grid from './components/Grid'; -import Header from './components/Header'; import LoadingIndicator from './components/LoadingIndicator'; import Row from './components/Row'; import SectionTitle from './components/SectionTitle'; @@ -15,8 +15,8 @@ import SignIn from './components/SignIn'; export {Alert}; export {Button}; export {Col}; +export {DrawerLayout}; export {Grid}; -export {Header}; export {LoadingIndicator}; export {Row}; export {SectionTitle}; diff --git a/src/reducers/session.js b/src/reducers/session.js index 6c6dcae..525a475 100755 --- a/src/reducers/session.js +++ b/src/reducers/session.js @@ -1,5 +1,6 @@ import { CLEAN_USER_VALIDATIONS, + FETCH_CURRENT_USER_SUCCEEDED, LAST_USER_LOGGED_REQUESTED, LAST_USER_LOGGED_SUCCEEDED, USER_LOGIN_FAILED, @@ -21,6 +22,8 @@ export default function (state = initialState, action) { switch (action.type) { case CLEAN_USER_VALIDATIONS: return {...state, ...initialState}; + case FETCH_CURRENT_USER_SUCCEEDED: + return {...state, user: action.user}; case LAST_USER_LOGGED_REQUESTED: return {...state, lastUserLogged: null}; case LAST_USER_LOGGED_SUCCEEDED: diff --git a/src/sagas/index.js b/src/sagas/index.js index b3f7491..8d2ac74 100644 --- a/src/sagas/index.js +++ b/src/sagas/index.js @@ -1,8 +1,9 @@ import {handleError} from './common'; import { - getLastUserLogged, getToken, fetchRefreshToken, refreshAccessToken, signIn + fetchCurrentUser, getLastUserLogged, getToken, fetchRefreshToken, refreshAccessToken, signIn } from './session'; +export {fetchCurrentUser}; export {fetchRefreshToken}; export {getLastUserLogged}; export {getToken}; diff --git a/src/sagas/session.js b/src/sagas/session.js index 9b3a2f8..913ca2d 100755 --- a/src/sagas/session.js +++ b/src/sagas/session.js @@ -7,6 +7,7 @@ import { notifyLoginSucceeded, notifyRefreshAccessToken, notifyFetchRefreshTokenSucceeded, + receiveCurrentUser, receiveLastUserLogged, requestClearUserData, requestFetchToken, @@ -91,3 +92,12 @@ export function* refreshAccessToken({authEndpoint, clientId, clientSecret}) { yield put(handleError(err)); } } + +export function* fetchCurrentUser() { + try { + const user = yield call(SessionService.fetchCurrentUser); + yield put(receiveCurrentUser(user)); + } catch (err) { + yield put(handleError(err)); + } +} diff --git a/src/services/session.js b/src/services/session.js index 551c200..d485233 100755 --- a/src/services/session.js +++ b/src/services/session.js @@ -69,4 +69,8 @@ export default class SessionService { isUserValid: true }; } + + static async fetchCurrentUser() { + return decode(await TokenService.getToken()); + } }