From 56b8f6e4797525d6a10aa46d668f6d6505ea3584 Mon Sep 17 00:00:00 2001 From: enahum Date: Wed, 14 Jun 2017 10:04:41 -0400 Subject: [PATCH] Handle the cases when a user with no teams (#621) --- app/actions/views/select_team.js | 5 ++ app/components/root/index.js | 3 + app/components/root/root.js | 51 ++++++++++++++- app/constants/navigation.js | 3 +- app/screens/index.js | 2 +- app/screens/load_team/index.js | 3 +- app/screens/load_team/load_team.js | 28 ++++---- app/screens/select_team/index.js | 9 ++- app/screens/select_team/select_team.js | 90 +++++++++++++++++++++++--- app/screens/settings/settings.js | 1 - assets/base/i18n/en.json | 1 + yarn.lock | 2 +- 12 files changed, 166 insertions(+), 32 deletions(-) diff --git a/app/actions/views/select_team.js b/app/actions/views/select_team.js index f9186e3c8..ecf736c87 100644 --- a/app/actions/views/select_team.js +++ b/app/actions/views/select_team.js @@ -4,6 +4,9 @@ import {batchActions} from 'redux-batched-actions'; import {ChannelTypes, TeamTypes} from 'mattermost-redux/action_types'; +import EventEmitter from 'mattermost-redux/utils/event_emitter'; + +import {NavigationTypes} from 'app/constants'; import {setChannelDisplayName} from './channel'; @@ -34,6 +37,8 @@ export function selectFirstAvailableTeam() { if (firstTeam) { handleTeamChange(firstTeam)(dispatch, getState); + } else { + EventEmitter.emit(NavigationTypes.NAVIGATION_NO_TEAMS); } }; } diff --git a/app/components/root/index.js b/app/components/root/index.js index 525595681..8d87ac665 100644 --- a/app/components/root/index.js +++ b/app/components/root/index.js @@ -5,8 +5,10 @@ import {connect} from 'react-redux'; import DeviceInfo from 'react-native-device-info'; import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels'; +import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general'; import {getTheme} from 'app/selectors/preferences'; +import {removeProtocol} from 'app/utils/url'; import Root from './root'; @@ -23,6 +25,7 @@ function mapStateToProps(state, ownProps) { ...ownProps, theme: getTheme(state), currentChannelId: getCurrentChannelId(state), + currentUrl: removeProtocol(getCurrentUrl(state)), locale }; } diff --git a/app/components/root/root.js b/app/components/root/root.js index 203d95aec..a093d814a 100644 --- a/app/components/root/root.js +++ b/app/components/root/root.js @@ -4,10 +4,11 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import {IntlProvider} from 'react-intl'; +import {Platform} from 'react-native'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; -import {ViewTypes} from 'app/constants'; +import {NavigationTypes, ViewTypes} from 'app/constants'; import {getTranslations} from 'app/i18n'; export default class Root extends PureComponent { @@ -16,6 +17,7 @@ export default class Root extends PureComponent { navigator: PropTypes.object, excludeEvents: PropTypes.bool, currentChannelId: PropTypes.string, + currentUrl: PropTypes.string, locale: PropTypes.string.isRequired, theme: PropTypes.object.isRequired }; @@ -24,6 +26,7 @@ export default class Root extends PureComponent { if (!this.props.excludeEvents) { EventEmitter.on(ViewTypes.NOTIFICATION_IN_APP, this.handleInAppNotification); EventEmitter.on(ViewTypes.NOTIFICATION_TAPPED, this.handleNotificationTapped); + EventEmitter.on(NavigationTypes.NAVIGATION_NO_TEAMS, this.handleNoTeams); } } @@ -31,6 +34,7 @@ export default class Root extends PureComponent { if (!this.props.excludeEvents) { EventEmitter.off(ViewTypes.NOTIFICATION_IN_APP, this.handleInAppNotification); EventEmitter.off(ViewTypes.NOTIFICATION_TAPPED, this.handleNotificationTapped); + EventEmitter.off(NavigationTypes.NAVIGATION_NO_TEAMS, this.handleNoTeams); } } @@ -51,6 +55,50 @@ export default class Root extends PureComponent { } }; + handleNoTeams = () => { + const {currentUrl, navigator, theme} = this.props; + const {intl} = this.refs.provider.getChildContext(); + + let navigatorButtons; + if (Platform.OS === 'android') { + navigatorButtons = { + rightButtons: [{ + title: intl.formatMessage({id: 'sidebar_right_menu.logout', defaultMessage: 'Logout'}), + id: 'logout', + buttonColor: theme.sidebarHeaderTextColor, + showAsAction: 'always' + }] + }; + } else { + navigatorButtons = { + leftButtons: [{ + title: intl.formatMessage({id: 'sidebar_right_menu.logout', defaultMessage: 'Logout'}), + id: 'logout', + buttonColor: theme.sidebarHeaderTextColor + }] + }; + } + + navigator.resetTo({ + screen: 'SelectTeam', + title: intl.formatMessage({id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'}), + animated: false, + backButtonTitle: '', + navigatorStyle: { + navBarTextColor: theme.sidebarHeaderTextColor, + navBarBackgroundColor: theme.sidebarHeaderBg, + navBarButtonColor: theme.sidebarHeaderTextColor, + screenBackgroundColor: theme.centerChannelBg + }, + navigatorButtons, + passProps: { + currentUrl, + userWithoutTeams: true, + theme + } + }); + }; + handleNotificationTapped = () => { const {navigator, theme} = this.props; @@ -71,6 +119,7 @@ export default class Root extends PureComponent { return ( diff --git a/app/constants/navigation.js b/app/constants/navigation.js index 66ea09f7f..cc5899560 100644 --- a/app/constants/navigation.js +++ b/app/constants/navigation.js @@ -5,7 +5,8 @@ import keyMirror from 'mattermost-redux/utils/key_mirror'; const NavigationTypes = keyMirror({ NAVIGATION_RESET: null, - NAVIGATION_CLOSE_MODAL: null + NAVIGATION_CLOSE_MODAL: null, + NAVIGATION_NO_TEAMS: null }); export default NavigationTypes; diff --git a/app/screens/index.js b/app/screens/index.js index 52f770fdd..8beb50ca6 100644 --- a/app/screens/index.js +++ b/app/screens/index.js @@ -57,7 +57,7 @@ export function registerScreens(store, Provider) { Navigation.registerComponent('CreateChannel', () => wrapWithContextProvider(CreateChannel), store, Provider); Navigation.registerComponent('EditPost', () => wrapWithContextProvider(EditPost), store, Provider); Navigation.registerComponent('ImagePreview', () => wrapWithContextProvider(ImagePreview), store, Provider); - Navigation.registerComponent('LoadTeam', () => LoadTeam, store, Provider); + Navigation.registerComponent('LoadTeam', () => wrapWithContextProvider(LoadTeam), store, Provider); Navigation.registerComponent('Login', () => wrapWithContextProvider(Login), store, Provider); Navigation.registerComponent('LoginOptions', () => wrapWithContextProvider(LoginOptions), store, Provider); Navigation.registerComponent('MFA', () => wrapWithContextProvider(Mfa), store, Provider); diff --git a/app/screens/load_team/index.js b/app/screens/load_team/index.js index 0673e6d06..4352a67c7 100644 --- a/app/screens/load_team/index.js +++ b/app/screens/load_team/index.js @@ -4,6 +4,7 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; +import {getTeams} from 'mattermost-redux/actions/teams'; import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams'; import {initialize} from 'app/actions/views/load_team'; @@ -18,7 +19,6 @@ function mapStateToProps(state, ownProps) { ...ownProps, config: state.entities.general.config, theme: getTheme(state), - teamsRequest: state.requests.teams.getMyTeams, teams: state.entities.teams.teams, currentTeam: getCurrentTeam(state), myMembers: state.entities.teams.myMembers, @@ -30,6 +30,7 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ clearNotification, + getTeams, goToNotification, handleTeamChange, initialize diff --git a/app/screens/load_team/load_team.js b/app/screens/load_team/load_team.js index 30bc8d841..6be5491c3 100644 --- a/app/screens/load_team/load_team.js +++ b/app/screens/load_team/load_team.js @@ -4,22 +4,24 @@ import {PureComponent} from 'react'; import PropTypes from 'prop-types'; -import {RequestStatus} from 'mattermost-redux/constants'; +import EventEmitter from 'mattermost-redux/utils/event_emitter'; + +import {NavigationTypes} from 'app/constants'; export default class LoadTeam extends PureComponent { static propTypes = { - navigator: PropTypes.object, - notification: PropTypes.object, - teams: PropTypes.object.isRequired, - myMembers: PropTypes.object.isRequired, - teamsRequest: PropTypes.object.isRequired, - currentTeam: PropTypes.object, actions: PropTypes.shape({ clearNotification: PropTypes.func.isRequired, + getTeams: PropTypes.func.isRequired, goToNotification: PropTypes.func.isRequired, handleTeamChange: PropTypes.func.isRequired, initialize: PropTypes.func.isRequired }).isRequired, + currentTeam: PropTypes.object, + myMembers: PropTypes.object.isRequired, + navigator: PropTypes.object, + notification: PropTypes.object, + teams: PropTypes.object.isRequired, theme: PropTypes.object.isRequired }; @@ -40,19 +42,17 @@ export default class LoadTeam extends PureComponent { return this.selectFirstTeam(teams, myMembers); } - componentWillReceiveProps(nextProps) { - if (this.props.teamsRequest.status === RequestStatus.STARTED && - nextProps.teamsRequest.status === RequestStatus.SUCCESS) { - this.selectFirstTeam(nextProps.teams, nextProps.myMembers); - } - } - selectFirstTeam(allTeams, myMembers) { const teams = Object.keys(myMembers).map((key) => allTeams[key]); const firstTeam = Object.values(teams).sort((a, b) => a.display_name.localeCompare(b.display_name))[0]; if (firstTeam) { this.onSelectTeam(firstTeam); + } else { + const {getTeams} = this.props.actions; + getTeams().then(() => { + EventEmitter.emit(NavigationTypes.NAVIGATION_NO_TEAMS); + }); } } diff --git a/app/screens/select_team/index.js b/app/screens/select_team/index.js index d93a9cc82..d49e5a983 100644 --- a/app/screens/select_team/index.js +++ b/app/screens/select_team/index.js @@ -4,10 +4,12 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; +import {initialize} from 'app/actions/views/load_team'; import {handleTeamChange} from 'app/actions/views/select_team'; import {markChannelAsRead} from 'mattermost-redux/actions/channels'; -import {getTeams, joinTeam} from 'mattermost-redux/actions/teams'; +import {joinTeam} from 'mattermost-redux/actions/teams'; +import {logout} from 'mattermost-redux/actions/users'; import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels'; import {getJoinableTeams} from 'mattermost-redux/selectors/entities/teams'; import {getCurrentUser} from 'mattermost-redux/selectors/entities/users'; @@ -22,7 +24,7 @@ function mapStateToProps(state, ownProps) { } return { - teamsRequest: state.requests.teams.getTeams, + teamsRequest: state.requests.teams.getMyTeams, teams: Object.values(getJoinableTeams(state)).sort(sortTeams), currentChannelId: getCurrentChannelId(state), ...ownProps @@ -32,9 +34,10 @@ function mapStateToProps(state, ownProps) { function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ - getTeams, handleTeamChange, + initialize, joinTeam, + logout, markChannelAsRead }, dispatch) }; diff --git a/app/screens/select_team/select_team.js b/app/screens/select_team/select_team.js index a0d292a38..04f3a8b91 100644 --- a/app/screens/select_team/select_team.js +++ b/app/screens/select_team/select_team.js @@ -23,14 +23,16 @@ import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; export default class SelectTeam extends PureComponent { static propTypes = { actions: PropTypes.shape({ - getTeams: PropTypes.func.isRequired, handleTeamChange: PropTypes.func.isRequired, + initialize: PropTypes.func.isRequired, joinTeam: PropTypes.func.isRequired, + logout: PropTypes.func.isRequired, markChannelAsRead: PropTypes.func.isRequired }).isRequired, currentChannelId: PropTypes.string, currentUrl: PropTypes.string.isRequired, navigator: PropTypes.object, + userWithoutTeams: PropTypes.bool, teams: PropTypes.array.isRequired, theme: PropTypes.object }; @@ -40,27 +42,73 @@ export default class SelectTeam extends PureComponent { props.navigator.setOnNavigatorEvent(this.onNavigatorEvent); this.state = { - joining: false + joining: false, + teams: null }; } + componentDidMount() { + this.buildData(this.props); + } + + componentWillReceiveProps(nextProps) { + if (this.props.teams !== nextProps.teams) { + this.buildData(nextProps); + } + } + + buildData = (props) => { + if (props.teams.length) { + this.setState({teams: props.teams}); + } else { + const teams = [{ + id: 'mobile.select_team.no_teams', + defaultMessage: 'There are no available teams for you to join.' + }]; + this.setState({teams}); + } + }; + close = () => { this.props.navigator.dismissModal({ animationType: 'slide-down' }); }; + goToChannelView = () => { + const {actions, navigator, theme} = this.props; + + actions.initialize(); + navigator.resetTo({ + screen: 'Channel', + animated: false, + navigatorStyle: { + navBarHidden: true, + statusBarHidden: false, + statusBarHideWithNavBar: false, + screenBackgroundColor: theme.centerChannelBg + } + }); + }; + onNavigatorEvent = (event) => { if (event.type === 'NavBarButtonPress') { - if (event.id === 'close-teams') { + const {logout} = this.props.actions; + + switch (event.id) { + case 'close-teams': this.close(); + break; + case 'logout': + InteractionManager.runAfterInteractions(logout); + break; } } }; onSelectTeam = async (team) => { this.setState({joining: true}); - const {currentChannelId} = this.props; + const {currentChannelId, userWithoutTeams} = this.props; const { joinTeam, handleTeamChange, @@ -72,16 +120,35 @@ export default class SelectTeam extends PureComponent { } await joinTeam(team.invite_id, team.id); handleTeamChange(team); - EventEmitter.emit('close_channel_drawer'); - InteractionManager.runAfterInteractions(() => { - this.close(); - }); + + if (userWithoutTeams) { + this.goToChannelView(); + } else { + EventEmitter.emit('close_channel_drawer'); + InteractionManager.runAfterInteractions(() => { + this.close(); + }); + } }; renderItem = ({item}) => { const {currentUrl, theme} = this.props; const styles = getStyleSheet(theme); + if (item.id === 'mobile.select_team.no_teams') { + return ( + + + + + + ); + } + return ( { justifyContent: 'center', width: 40 }, + noTeam: { + color: theme.centerChannelColor, + fontSize: 14 + }, teamIcon: { color: theme.buttonColor, fontFamily: 'OpenSans', diff --git a/app/screens/settings/settings.js b/app/screens/settings/settings.js index 1c0a3608d..abe27932d 100644 --- a/app/screens/settings/settings.js +++ b/app/screens/settings/settings.js @@ -97,7 +97,6 @@ class Settings extends PureComponent { navigator.push({ screen: 'SelectTeam', title: intl.formatMessage({id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'}), - animationType: 'slide-up', animated: true, backButtonTitle: '', navigatorStyle: { diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 5a5d5247e..837abbb2d 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -1767,6 +1767,7 @@ "mobile.routes.user_profile.send_message": "Send Message", "mobile.select_team.choose": "Your teams:", "mobile.select_team.join_open": "Open teams you can join", + "mobile.select_team.no_teams": "There are no available teams for you to join.", "mobile.server_ping_failed": "Cannot connect to the server. Please check your server URL and internet connection.", "mobile.server_url.invalid_format": "URL must start with http:// or https://", "mobile.session_expired": "Session Expired: Please log in to continue receiving notifications.", diff --git a/yarn.lock b/yarn.lock index 1fb0f70fb..cc102ffd0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3645,7 +3645,7 @@ makeerror@1.0.x: mattermost-redux@mattermost/mattermost-redux#master: version "0.0.1" - resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/909f75d50678998a1fcb9d3b35d1fae9e918147f" + resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/fe0d67308436b7b501bc63b287f5434180f63a6c" dependencies: deep-equal "1.0.1" harmony-reflect "1.5.1"