From de834cd586f3e7cdc6fef2a41bb05cb8e6b6f004 Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Tue, 14 Jul 2020 22:31:54 -0500 Subject: [PATCH] Updated Introduction/onboarding flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Updated: - Introduction/onboarding flow • Added: - autoJoinGroup to default "Introduce Yourself" group in gab.com if you post the welcome post in introduction last slide --- app/controllers/api/v1/statuses_controller.rb | 2 + app/javascript/gabsocial/actions/compose.js | 6 +- .../gabsocial/actions/onboarding.js | 3 - app/javascript/gabsocial/constants.js | 5 +- .../gabsocial/containers/gabsocial.js | 41 +++- .../compose/components/compose_form.js | 5 +- .../containers/compose_form_container.js | 4 +- .../gabsocial/features/introduction.js | 199 ++++++++++++++---- .../gabsocial/layouts/introduction_layout.js | 32 ++- app/javascript/gabsocial/reducers/settings.js | 2 +- app/services/post_status_service.rb | 2 + 11 files changed, 236 insertions(+), 65 deletions(-) diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index cdee103e..96930bb4 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -56,6 +56,7 @@ class Api::V1::StatusesController < Api::BaseController @status = PostStatusService.new.call(current_user.account, text: status_params[:status], markdown: markdown, + autoJoinGroup: status_params[:autoJoinGroup], thread: status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id]), media_ids: status_params[:media_ids], sensitive: status_params[:sensitive], @@ -109,6 +110,7 @@ class Api::V1::StatusesController < Api::BaseController params.permit( :status, :markdown, + :autoJoinGroup, :in_reply_to_id, :quote_of_id, :sensitive, diff --git a/app/javascript/gabsocial/actions/compose.js b/app/javascript/gabsocial/actions/compose.js index 860f6268..ac562ac7 100644 --- a/app/javascript/gabsocial/actions/compose.js +++ b/app/javascript/gabsocial/actions/compose.js @@ -7,6 +7,7 @@ import { isMobile } from '../utils/is_mobile' import { search as emojiSearch } from '../components/emoji/emoji_mart_search_light'; import { urlRegex } from '../features/ui/util/url_regex' import { tagHistory } from '../settings'; +import { joinGroup } from './groups' import { useEmoji } from './emojis'; import resizeImage from '../utils/resize_image'; import { importFetchedAccounts } from './importer'; @@ -260,10 +261,12 @@ export function handleComposeSubmit(dispatch, getState, response, status) { } } -export function submitCompose(groupId, replyToId = null, router, isStandalone) { +export function submitCompose(groupId, replyToId = null, router, isStandalone, autoJoinGroup) { return function (dispatch, getState) { if (!me) return; + if (autoJoinGroup) dispatch(joinGroup(groupId)) + let status = getState().getIn(['compose', 'text'], ''); let markdown = getState().getIn(['compose', 'markdown'], ''); const media = getState().getIn(['compose', 'media_attachments']); @@ -304,6 +307,7 @@ export function submitCompose(groupId, replyToId = null, router, isStandalone) { status, markdown, scheduled_at, + autoJoinGroup, in_reply_to_id: inReplyToId, quote_of_id: getState().getIn(['compose', 'quote_of_id'], null), media_ids: media.map(item => item.get('id')), diff --git a/app/javascript/gabsocial/actions/onboarding.js b/app/javascript/gabsocial/actions/onboarding.js index cf8c2d9b..52733a44 100644 --- a/app/javascript/gabsocial/actions/onboarding.js +++ b/app/javascript/gabsocial/actions/onboarding.js @@ -1,8 +1,5 @@ -import moment from 'moment-mini' import { changeSetting, saveSettings } from './settings' -export const MIN_ACCOUNT_CREATED_AT_ONBOARDING = moment('2020-07-14').valueOf() - export const saveShownOnboarding = () => (dispatch) => { dispatch(changeSetting(['shownOnboarding'], true)) dispatch(saveSettings()) diff --git a/app/javascript/gabsocial/constants.js b/app/javascript/gabsocial/constants.js index c7c046a9..9f9d2fca 100644 --- a/app/javascript/gabsocial/constants.js +++ b/app/javascript/gabsocial/constants.js @@ -108,4 +108,7 @@ export const NOTIFICATION_FILTERS = [ 'follow_requests', ] -export const GAB_COM_INTRODUCE_YOURSELF_GROUP_ID = '12' \ No newline at end of file +export const GAB_COM_INTRODUCE_YOURSELF_GROUP_ID = '12' + +// export const MIN_ACCOUNT_CREATED_AT_ONBOARDING = 1594789200000 // 2020-07-15 +export const MIN_ACCOUNT_CREATED_AT_ONBOARDING = 1594782839825 // 2020-07-14 \ No newline at end of file diff --git a/app/javascript/gabsocial/containers/gabsocial.js b/app/javascript/gabsocial/containers/gabsocial.js index e607493e..2a04acb0 100644 --- a/app/javascript/gabsocial/containers/gabsocial.js +++ b/app/javascript/gabsocial/containers/gabsocial.js @@ -3,18 +3,21 @@ import { Provider } from 'react-redux' import configureStore from '../store/configureStore' import { BrowserRouter, Route } from 'react-router-dom' +import moment from 'moment-mini' import { ScrollContext } from 'react-router-scroll-4' import { IntlProvider, addLocaleData } from 'react-intl' import { fetchCustomEmojis } from '../actions/custom_emojis' import { hydrateStore } from '../actions/store' +import { MIN_ACCOUNT_CREATED_AT_ONBOARDING } from '../constants' import { connectUserStream, connectStatusUpdateStream, } from '../actions/streaming' import { getLocale } from '../locales' import initialState from '../initial_state' -import { me } from '../initial_state' +import { me, isFirstSession } from '../initial_state' import UI from '../features/ui' +import IntroductionPage from '../pages/introduction_page' import ErrorBoundary from '../components/error_boundary' import Display from './display' @@ -27,9 +30,45 @@ const hydrateAction = hydrateStore(initialState) store.dispatch(hydrateAction) store.dispatch(fetchCustomEmojis()) +const mapStateToProps = (state) => ({ + accountCreatedAt: !!me ? state.getIn(['accounts', me, 'created_at']) : undefined, + shownOnboarding: state.getIn(['settings', 'shownOnboarding']), +}) + +@connect(mapStateToProps) class GabSocialMount extends PureComponent { + static propTypes = { + shownOnboarding: PropTypes.bool.isRequired, + accountCreatedAt: PropTypes.string, + } + + state = { + shownOnboarding: this.props.shownOnboarding, + shouldShow: false, + } + + componentDidMount() { + if (!!me && this.props.accountCreatedAt) { + //If first time opening app, and is new user, show onboarding + const accountCreatedAtValue = moment(this.props.accountCreatedAt).valueOf() + const shouldShow = isFirstSession && !this.state.shownOnboarding && accountCreatedAtValue > MIN_ACCOUNT_CREATED_AT_ONBOARDING + + if (shouldShow) this.setState({ shouldShow }) + } + } + render () { + const { shownOnboarding, shouldShow } = this.state + + if (!shownOnboarding && shouldShow) { + return ( + + + + ) + } + return ( diff --git a/app/javascript/gabsocial/features/compose/components/compose_form.js b/app/javascript/gabsocial/features/compose/components/compose_form.js index c5f4005a..30a36987 100644 --- a/app/javascript/gabsocial/features/compose/components/compose_form.js +++ b/app/javascript/gabsocial/features/compose/components/compose_form.js @@ -90,6 +90,7 @@ class ComposeForm extends ImmutablePureComponent { selectedGifSrc: PropTypes.string, isPro: PropTypes.bool, hidePro: PropTypes.bool, + autoJoinGroup: PropTypes.bool, } static defaultProps = { @@ -142,14 +143,14 @@ class ComposeForm extends ImmutablePureComponent { // } // Submit disabled: - const { isSubmitting, isChangingUpload, isUploading, anyMedia, groupId } = this.props; + const { isSubmitting, isChangingUpload, isUploading, anyMedia, groupId, autoJoinGroup } = this.props const fulltext = [this.props.spoilerText, countableText(this.props.text)].join(''); if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > MAX_POST_CHARACTER_COUNT || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) { return; } - this.props.onSubmit(groupId, this.props.replyToId, this.context.router); + this.props.onSubmit(groupId, this.props.replyToId, this.context.router, autoJoinGroup) } onSuggestionsClearRequested = () => { diff --git a/app/javascript/gabsocial/features/compose/containers/compose_form_container.js b/app/javascript/gabsocial/features/compose/containers/compose_form_container.js index 9f336551..18ae2ac1 100644 --- a/app/javascript/gabsocial/features/compose/containers/compose_form_container.js +++ b/app/javascript/gabsocial/features/compose/containers/compose_form_container.js @@ -102,8 +102,8 @@ const mapDispatchToProps = (dispatch, { isStandalone }) => ({ dispatch(changeCompose(text, markdown, newReplyToId, isStandalone, position)) }, - onSubmit(groupId, replyToId, router) { - dispatch(submitCompose(groupId, replyToId, router, isStandalone)) + onSubmit(groupId, replyToId, router, autoJoinGroup) { + dispatch(submitCompose(groupId, replyToId, router, isStandalone, autoJoinGroup)) }, onClearSuggestions() { diff --git a/app/javascript/gabsocial/features/introduction.js b/app/javascript/gabsocial/features/introduction.js index 92671e24..8bedafd1 100644 --- a/app/javascript/gabsocial/features/introduction.js +++ b/app/javascript/gabsocial/features/introduction.js @@ -1,17 +1,22 @@ +import { Fragment } from 'react' import ReactSwipeableViews from 'react-swipeable-views' import ImmutablePropTypes from 'react-immutable-proptypes' import ImmutablePureComponent from 'react-immutable-pure-component' -import { CX } from '../constants' +import { + CX, + BREAKPOINT_EXTRA_SMALL, + GAB_COM_INTRODUCE_YOURSELF_GROUP_ID, +} from '../constants' import { me } from '../initial_state' import { saveShownOnboarding } from '../actions/onboarding' import { fetchGroups } from '../actions/groups' +import { saveUserProfileInformation } from '../actions/user' import { changeCompose, clearCompose, } from '../actions/compose' import { makeGetAccount } from '../selectors' import Button from '../components/button' -import DisplayName from '../components/display_name' import Divider from '../components/divider' import FileInput from '../components/file_input' import GroupListItem from '../components/group_list_item' @@ -21,12 +26,15 @@ import Image from '../components/image' import Input from '../components/input' import Text from '../components/text' import ComposeFormContainer from './compose/containers/compose_form_container' +import Responsive from './ui/util/responsive_component' class SlideWelcome extends PureComponent { render() { return (
+ Welcome to Gab +
Gab is the home of free speech online and a place where users shape their own experience. @@ -41,10 +49,6 @@ class SlideWelcome extends PureComponent {
-
) } @@ -57,35 +61,72 @@ class SlidePhotos extends ImmutablePureComponent { account: ImmutablePropTypes.map.isRequired, } + state = { + displayNameValue: this.props.account.get('display_name'), + } + + handleCoverPhotoChange = (e) => { + try { + this.props.onSave({ header: e.target.files[0] }) + } catch (error) { + // + } + } + + handleProfilePhotoChange = (e) => { + try { + this.props.onSave({ avatar: e.target.files[0] }) + } catch (error) { + // + } + } + + handleDisplayNameChange = (value) => { + this.setState({ displayNameValue: value }) + } + + handleDisplayNameBlur = () => { + this.props.onSave({ + displayName: this.state.displayNameValue, + }) + } + render() { - const { account } = this.props + const { displayNameValue } = this.state return (
-
+
Set your cover photo, profile photo and enter your display name so people can find you.
-
- + -
- +
@@ -119,6 +160,7 @@ class SlideGroups extends ImmutablePureComponent { groupIds.map((groupId, i) => (
- Now let's make your very first Gab post! Please introduce yourself to the Gab community. How did you hear about Gab? What are you interested in? -
+ { + !submitted && + + Now let's make your very first Gab post! Please introduce yourself to the Gab community. How did you hear about Gab? What are you interested in? +
- + -
- -
+
+ +
+
+ } + { + submitted && + + Your gab was posted! +
+ Click the checkbox in the top right to go to your home page. +
+ +
+ }
@@ -163,13 +233,20 @@ const mapStateToProps = (state) => ({ account: makeGetAccount()(state, me), groupIds: state.getIn(['group_lists', 'featured', 'items']), shownOnboarding: state.getIn(['settings', 'shownOnboarding'], false), + isSubmitting: state.getIn(['compose', 'is_submitting']), }) const mapDispatchToProps = (dispatch) => ({ onClearCompose: () => dispatch(clearCompose()), onSaveShownOnboarding: () => dispatch(saveShownOnboarding()), onFetchFeaturedGroups: () => dispatch(fetchGroups('featured')), - setPlaceholderCompose: () => dispatch(changeCompose('Hello everyone, I just joined Gab! I’m looking forward to speaking freely and meeting new friends.')) + setPlaceholderCompose() { + const defaultMessage = 'Hello everyone, I just joined Gab! I’m looking forward to speaking freely and meeting new friends.' + dispatch(changeCompose(defaultMessage)) + }, + onSaveUserProfileInformation(data) { + dispatch(saveUserProfileInformation(data)) + }, }) export default @@ -178,25 +255,31 @@ class Introduction extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, - dispatch: PropTypes.func.isRequired, groupIds: ImmutablePropTypes.list, + isSubmitting: PropTypes.bool.isRequired, shownOnboarding: PropTypes.bool.isRequired, setPlaceholderCompose: PropTypes.func.isRequired, onClearCompose: PropTypes.func.isRequired, onSaveShownOnboarding: PropTypes.func.isRequired, onFetchFeaturedGroups: PropTypes.func.isRequired, + onSaveUserProfileInformation: PropTypes.func.isRequired, } state = { currentIndex: 0, + submittedFirstPost: false, } componentDidMount() { - if (!this.props.shownOnboarding) { - window.addEventListener('keyup', this.handleKeyUp) - this.props.onFetchFeaturedGroups() - this.props.setPlaceholderCompose() - this.props.onSaveShownOnboarding() + window.addEventListener('keyup', this.handleKeyUp) + this.props.onFetchFeaturedGroups() + this.props.setPlaceholderCompose() + this.props.onSaveShownOnboarding() + } + + componentDidUpdate(prevProps) { + if (!this.state.submittedFirstPost && !prevProps.isSubmitting && this.props.isSubmitting) { + this.setState({ submittedFirstPost: true }) } } @@ -223,7 +306,7 @@ class Introduction extends ImmutablePureComponent { currentIndex: newIndex, }) - if (newIndex === 3) { + if (newIndex === 4) { this.props.onClearCompose() } } @@ -243,15 +326,25 @@ class Introduction extends ImmutablePureComponent { } } + handleOnSaveUserProfileInformation = (data) => { + this.props.onSaveUserProfileInformation(data) + } + render() { const { account, groupIds } = this.props - const { currentIndex } = this.state + const { currentIndex, submittedFirstPost } = this.state const pages = [ , - , + , , - , + , ] const titles = [ @@ -280,24 +373,38 @@ class Introduction extends ImmutablePureComponent { ) }) - + return (
- - {title} - + + + {title} + + + + + {title} + +
@@ -341,7 +448,7 @@ class Introduction extends ImmutablePureComponent { {pagination}