Handle the cases when a user with no teams (#621)
This commit is contained in:
parent
27c29e09b4
commit
56b8f6e479
12 changed files with 166 additions and 32 deletions
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<IntlProvider
|
||||
ref='provider'
|
||||
locale={locale}
|
||||
messages={getTranslations(locale)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<View style={styles.teamWrapper}>
|
||||
<View style={styles.teamContainer}>
|
||||
<FormattedText
|
||||
id={item.id}
|
||||
defaultMessage={item.defaultMessage}
|
||||
style={styles.noTeam}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.teamWrapper}>
|
||||
<TouchableOpacity
|
||||
|
|
@ -116,7 +183,8 @@ export default class SelectTeam extends PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const {teams, theme} = this.props;
|
||||
const {theme} = this.props;
|
||||
const {teams} = this.state;
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
if (this.state.joining) {
|
||||
|
|
@ -191,6 +259,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
justifyContent: 'center',
|
||||
width: 40
|
||||
},
|
||||
noTeam: {
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 14
|
||||
},
|
||||
teamIcon: {
|
||||
color: theme.buttonColor,
|
||||
fontFamily: 'OpenSans',
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue