diff --git a/components/channel_view/channel_view.jsx b/components/channel_view/channel_view.jsx index a457b142891e..4bbfdc9a2f32 100644 --- a/components/channel_view/channel_view.jsx +++ b/components/channel_view/channel_view.jsx @@ -29,12 +29,15 @@ export default class ChannelView extends React.PureComponent { }).isRequired, showTutorial: PropTypes.bool.isRequired, showNextSteps: PropTypes.bool.isRequired, + showNextStepsTips: PropTypes.bool.isRequired, + isOnboardingHidden: PropTypes.bool.isRequired, showNextStepsEphemeral: PropTypes.bool.isRequired, channelIsArchived: PropTypes.bool.isRequired, viewArchivedChannels: PropTypes.bool.isRequired, actions: PropTypes.shape({ goToLastViewedChannel: PropTypes.func.isRequired, setShowNextStepsView: PropTypes.func.isRequired, + getProfiles: PropTypes.func.isRequired, }), isCloud: PropTypes.bool.isRequired, }; @@ -97,8 +100,9 @@ export default class ChannelView extends React.PureComponent { this.props.actions.goToLastViewedChannel(); } - componentDidMount() { - if (this.props.showNextSteps) { + async componentDidMount() { + await this.props.actions.getProfiles(); + if ((this.props.showNextSteps || this.props.showNextStepsTips) && !this.props.isOnboardingHidden) { this.props.actions.setShowNextStepsView(true); } } diff --git a/components/channel_view/channel_view.test.jsx b/components/channel_view/channel_view.test.jsx index 2b2b219f1ffd..13c3556de357 100644 --- a/components/channel_view/channel_view.test.jsx +++ b/components/channel_view/channel_view.test.jsx @@ -17,6 +17,8 @@ describe('components/channel_view', () => { }, showTutorial: false, showNextSteps: false, + showNextStepsTips: false, + isOnboardingHidden: true, showNextStepsEphemeral: false, channelIsArchived: false, viewArchivedChannels: false, @@ -24,6 +26,7 @@ describe('components/channel_view', () => { actions: { goToLastViewedChannel: jest.fn(), setShowNextStepsView: jest.fn(), + getProfiles: jest.fn(), }, }; diff --git a/components/channel_view/index.js b/components/channel_view/index.js index e48c36275654..040cee1f45ba 100644 --- a/components/channel_view/index.js +++ b/components/channel_view/index.js @@ -12,12 +12,14 @@ import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general'; import {withRouter} from 'react-router-dom'; +import {getProfiles} from 'mattermost-redux/actions/users'; + import {getDirectTeammate} from 'utils/utils.jsx'; import {TutorialSteps, Preferences} from 'utils/constants'; import {goToLastViewedChannel} from 'actions/views/channel'; import {setShowNextStepsView} from 'actions/views/next_steps'; -import {showNextSteps} from 'components/next_steps_view/steps'; +import {isOnboardingHidden, showNextSteps, showNextStepsTips} from 'components/next_steps_view/steps'; import ChannelView from './channel_view.jsx'; @@ -60,6 +62,8 @@ function mapStateToProps(state) { deactivatedChannel: channel ? getDeactivatedChannel(state, channel.id) : false, showTutorial: enableTutorial && tutorialStep <= TutorialSteps.INTRO_SCREENS, showNextSteps: showNextSteps(state), + showNextStepsTips: showNextStepsTips(state), + isOnboardingHidden: isOnboardingHidden(state), showNextStepsEphemeral: state.views.nextSteps.show, channelIsArchived: channel ? channel.delete_at !== 0 : false, viewArchivedChannels, @@ -72,6 +76,7 @@ function mapDispatchToProps(dispatch) { actions: bindActionCreators({ setShowNextStepsView, goToLastViewedChannel, + getProfiles, }, dispatch), }; } diff --git a/components/main_menu/index.jsx b/components/main_menu/index.jsx index 93424ea4eccd..74251dd624c0 100644 --- a/components/main_menu/index.jsx +++ b/components/main_menu/index.jsx @@ -4,7 +4,7 @@ import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; -import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general'; +import {getConfig} from 'mattermost-redux/selectors/entities/general'; import { getMyTeams, getJoinableTeamIds, @@ -24,7 +24,11 @@ import {showMentions, showFlaggedPosts, closeRightHandSide, closeMenu as closeRh import {openModal} from 'actions/views/modals'; import {getRhsState} from 'selectors/rhs'; -import {nextStepsNotFinished} from 'components/next_steps_view/steps'; +import { + showOnboarding, + showNextStepsTips, + showNextSteps, +} from 'components/next_steps_view/steps'; import MainMenu from './main_menu.jsx'; @@ -32,7 +36,6 @@ function mapStateToProps(state) { const config = getConfig(state); const currentTeam = getCurrentTeam(state); const currentUser = getCurrentUser(state); - const license = getLicense(state); const appDownloadLink = config.AppDownloadLink; const enableCommands = config.EnableCommands === 'true'; @@ -92,13 +95,14 @@ function mapStateToProps(state) { currentUser, isMentionSearch: rhsState === RHSStates.MENTION, teamIsGroupConstrained: Boolean(currentTeam.group_constrained), - isLicensedForLDAPGroups: state.entities.general.license.LDAPGroups === 'true', + isLicensedForLDAPGroups: + state.entities.general.license.LDAPGroups === 'true', userLimit: getConfig(state).ExperimentalCloudUserLimit, currentUsers: state.entities.admin.analytics.TOTAL_USERS, - userIsAdmin: isAdmin( - getMyTeamMember(state, currentTeam.id).roles, - ), - showGettingStarted: !state.views.nextSteps.show && nextStepsNotFinished(state) && license.Cloud === 'true', + userIsAdmin: isAdmin(getMyTeamMember(state, currentTeam.id).roles), + showGettingStarted: showOnboarding(state), + showNextStepsTips: showNextStepsTips(state), + showNextSteps: showNextSteps(state), }; } diff --git a/components/main_menu/main_menu.jsx b/components/main_menu/main_menu.jsx index 0ab0780fade5..233911df10a6 100644 --- a/components/main_menu/main_menu.jsx +++ b/components/main_menu/main_menu.jsx @@ -65,6 +65,7 @@ class MainMenu extends React.PureComponent { userIsAdmin: PropTypes.bool, showGettingStarted: PropTypes.bool.isRequired, intl: intlShape.isRequired, + showNextStepsTips: PropTypes.bool, actions: PropTypes.shape({ openModal: PropTypes.func.isRequred, showMentions: PropTypes.func, @@ -371,7 +372,7 @@ class MainMenu extends React.PureComponent { id='gettingStarted' show={this.props.showGettingStarted} onClick={() => this.props.actions.unhideNextSteps()} - text={formatMessage({id: 'navbar_dropdown.gettingStarted', defaultMessage: 'Getting Started'})} + text={formatMessage({id: this.props.showNextStepsTips ? 'sidebar_next_steps.tipsAndNextSteps' : 'navbar_dropdown.gettingStarted', defaultMessage: this.props.showNextStepsTips ? 'Tips & Next Steps' : 'Getting Started'})} /> { const wrapper = shallow( , ); + wrapper.setState({show: true}); expect(wrapper).toMatchSnapshot(); }); diff --git a/components/next_steps_view/next_steps_view.tsx b/components/next_steps_view/next_steps_view.tsx index a9b8068334d7..068a9a629111 100644 --- a/components/next_steps_view/next_steps_view.tsx +++ b/components/next_steps_view/next_steps_view.tsx @@ -12,7 +12,7 @@ import {pageVisited, trackEvent} from 'actions/diagnostics_actions'; import Accordion from 'components/accordion'; import Card from 'components/card/card'; import {getAnalyticsCategory} from 'components/next_steps_view/step_helpers'; -import {Preferences} from 'utils/constants'; +import {Preferences, RecommendedNextSteps} from 'utils/constants'; import loadingIcon from 'images/spinner-48x48-blue.apng'; @@ -43,31 +43,37 @@ type State = { showFinalScreen: boolean; showTransitionScreen: boolean; animating: boolean; + show: boolean; } export default class NextStepsView extends React.PureComponent { constructor(props: Props) { super(props); - this.state = { showFinalScreen: false, showTransitionScreen: false, animating: false, + show: false, }; } async componentDidMount() { await this.props.actions.getProfiles(); - pageVisited(getAnalyticsCategory(this.props.isFirstAdmin), 'pageview_welcome'); - // If all steps are complete, don't render this and skip to the tips screen - if (this.getIncompleteStep() === null) { + // If all steps are complete, or user has skipped, don't render this and skip to the tips screen + if (this.getIncompleteStep() === null || this.checkStepsSkipped()) { this.showFinalScreenNoAnimation(); } - + // eslint-disable-next-line react/no-did-mount-set-state + this.setState({show: true}); + pageVisited(getAnalyticsCategory(this.props.isFirstAdmin), 'pageview_welcome'); this.props.actions.closeRightHandSide(); } + checkStepsSkipped = () => { + return this.props.preferences.some((pref) => pref.name === RecommendedNextSteps.SKIP && pref.value === 'true'); + } + getStartingStep = () => { for (let i = 0; i < this.props.steps.length; i++) { if (!this.isStepComplete(this.props.steps[i].id)) { @@ -102,6 +108,16 @@ export default class NextStepsView extends React.PureComponent { }; } + onSkipAll = async () => { + this.showFinalScreen(); + this.props.actions.savePreferences(this.props.currentUser.id, [{ + user_id: this.props.currentUser.id, + category: Preferences.RECOMMENDED_NEXT_STEPS, + name: RecommendedNextSteps.SKIP, + value: 'true', + }]); + } + onFinish = (setExpanded: (expandedKey: string) => void) => { return async (id: string) => { const stepIndex = this.getStepNumberFromId(id); @@ -120,7 +136,7 @@ export default class NextStepsView extends React.PureComponent { showFinalScreenNoAnimation = () => { pageVisited(getAnalyticsCategory(this.props.isFirstAdmin), 'pageview_tips_next_steps'); - this.setState({showFinalScreen: true}); + this.setState({showFinalScreen: true, animating: false}); } showFinalScreen = () => { @@ -287,7 +303,7 @@ export default class NextStepsView extends React.PureComponent {