Updated Introduction/onboarding flow

• Updated:
- Introduction/onboarding flow

• Added:
- autoJoinGroup to default "Introduce Yourself" group in gab.com if you post the welcome post in introduction last slide
This commit is contained in:
mgabdev 2020-07-14 22:31:54 -05:00
parent 0eb3ae2dfa
commit de834cd586
11 changed files with 236 additions and 65 deletions

View File

@ -56,6 +56,7 @@ class Api::V1::StatusesController < Api::BaseController
@status = PostStatusService.new.call(current_user.account, @status = PostStatusService.new.call(current_user.account,
text: status_params[:status], text: status_params[:status],
markdown: markdown, markdown: markdown,
autoJoinGroup: status_params[:autoJoinGroup],
thread: status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id]), thread: status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id]),
media_ids: status_params[:media_ids], media_ids: status_params[:media_ids],
sensitive: status_params[:sensitive], sensitive: status_params[:sensitive],
@ -109,6 +110,7 @@ class Api::V1::StatusesController < Api::BaseController
params.permit( params.permit(
:status, :status,
:markdown, :markdown,
:autoJoinGroup,
:in_reply_to_id, :in_reply_to_id,
:quote_of_id, :quote_of_id,
:sensitive, :sensitive,

View File

@ -7,6 +7,7 @@ import { isMobile } from '../utils/is_mobile'
import { search as emojiSearch } from '../components/emoji/emoji_mart_search_light'; import { search as emojiSearch } from '../components/emoji/emoji_mart_search_light';
import { urlRegex } from '../features/ui/util/url_regex' import { urlRegex } from '../features/ui/util/url_regex'
import { tagHistory } from '../settings'; import { tagHistory } from '../settings';
import { joinGroup } from './groups'
import { useEmoji } from './emojis'; import { useEmoji } from './emojis';
import resizeImage from '../utils/resize_image'; import resizeImage from '../utils/resize_image';
import { importFetchedAccounts } from './importer'; 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) { return function (dispatch, getState) {
if (!me) return; if (!me) return;
if (autoJoinGroup) dispatch(joinGroup(groupId))
let status = getState().getIn(['compose', 'text'], ''); let status = getState().getIn(['compose', 'text'], '');
let markdown = getState().getIn(['compose', 'markdown'], ''); let markdown = getState().getIn(['compose', 'markdown'], '');
const media = getState().getIn(['compose', 'media_attachments']); const media = getState().getIn(['compose', 'media_attachments']);
@ -304,6 +307,7 @@ export function submitCompose(groupId, replyToId = null, router, isStandalone) {
status, status,
markdown, markdown,
scheduled_at, scheduled_at,
autoJoinGroup,
in_reply_to_id: inReplyToId, in_reply_to_id: inReplyToId,
quote_of_id: getState().getIn(['compose', 'quote_of_id'], null), quote_of_id: getState().getIn(['compose', 'quote_of_id'], null),
media_ids: media.map(item => item.get('id')), media_ids: media.map(item => item.get('id')),

View File

@ -1,8 +1,5 @@
import moment from 'moment-mini'
import { changeSetting, saveSettings } from './settings' import { changeSetting, saveSettings } from './settings'
export const MIN_ACCOUNT_CREATED_AT_ONBOARDING = moment('2020-07-14').valueOf()
export const saveShownOnboarding = () => (dispatch) => { export const saveShownOnboarding = () => (dispatch) => {
dispatch(changeSetting(['shownOnboarding'], true)) dispatch(changeSetting(['shownOnboarding'], true))
dispatch(saveSettings()) dispatch(saveSettings())

View File

@ -109,3 +109,6 @@ export const NOTIFICATION_FILTERS = [
] ]
export const GAB_COM_INTRODUCE_YOURSELF_GROUP_ID = '12' 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

View File

@ -3,18 +3,21 @@
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import configureStore from '../store/configureStore' import configureStore from '../store/configureStore'
import { BrowserRouter, Route } from 'react-router-dom' import { BrowserRouter, Route } from 'react-router-dom'
import moment from 'moment-mini'
import { ScrollContext } from 'react-router-scroll-4' import { ScrollContext } from 'react-router-scroll-4'
import { IntlProvider, addLocaleData } from 'react-intl' import { IntlProvider, addLocaleData } from 'react-intl'
import { fetchCustomEmojis } from '../actions/custom_emojis' import { fetchCustomEmojis } from '../actions/custom_emojis'
import { hydrateStore } from '../actions/store' import { hydrateStore } from '../actions/store'
import { MIN_ACCOUNT_CREATED_AT_ONBOARDING } from '../constants'
import { import {
connectUserStream, connectUserStream,
connectStatusUpdateStream, connectStatusUpdateStream,
} from '../actions/streaming' } from '../actions/streaming'
import { getLocale } from '../locales' import { getLocale } from '../locales'
import initialState from '../initial_state' import initialState from '../initial_state'
import { me } from '../initial_state' import { me, isFirstSession } from '../initial_state'
import UI from '../features/ui' import UI from '../features/ui'
import IntroductionPage from '../pages/introduction_page'
import ErrorBoundary from '../components/error_boundary' import ErrorBoundary from '../components/error_boundary'
import Display from './display' import Display from './display'
@ -27,9 +30,45 @@ const hydrateAction = hydrateStore(initialState)
store.dispatch(hydrateAction) store.dispatch(hydrateAction)
store.dispatch(fetchCustomEmojis()) 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 { 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 () { render () {
const { shownOnboarding, shouldShow } = this.state
if (!shownOnboarding && shouldShow) {
return (
<BrowserRouter>
<Route path='/' component={IntroductionPage} />
</BrowserRouter>
)
}
return ( return (
<BrowserRouter> <BrowserRouter>
<ScrollContext> <ScrollContext>

View File

@ -90,6 +90,7 @@ class ComposeForm extends ImmutablePureComponent {
selectedGifSrc: PropTypes.string, selectedGifSrc: PropTypes.string,
isPro: PropTypes.bool, isPro: PropTypes.bool,
hidePro: PropTypes.bool, hidePro: PropTypes.bool,
autoJoinGroup: PropTypes.bool,
} }
static defaultProps = { static defaultProps = {
@ -142,14 +143,14 @@ class ComposeForm extends ImmutablePureComponent {
// } // }
// Submit disabled: // 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(''); 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)) { if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > MAX_POST_CHARACTER_COUNT || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
return; return;
} }
this.props.onSubmit(groupId, this.props.replyToId, this.context.router); this.props.onSubmit(groupId, this.props.replyToId, this.context.router, autoJoinGroup)
} }
onSuggestionsClearRequested = () => { onSuggestionsClearRequested = () => {

View File

@ -102,8 +102,8 @@ const mapDispatchToProps = (dispatch, { isStandalone }) => ({
dispatch(changeCompose(text, markdown, newReplyToId, isStandalone, position)) dispatch(changeCompose(text, markdown, newReplyToId, isStandalone, position))
}, },
onSubmit(groupId, replyToId, router) { onSubmit(groupId, replyToId, router, autoJoinGroup) {
dispatch(submitCompose(groupId, replyToId, router, isStandalone)) dispatch(submitCompose(groupId, replyToId, router, isStandalone, autoJoinGroup))
}, },
onClearSuggestions() { onClearSuggestions() {

View File

@ -1,17 +1,22 @@
import { Fragment } from 'react'
import ReactSwipeableViews from 'react-swipeable-views' import ReactSwipeableViews from 'react-swipeable-views'
import ImmutablePropTypes from 'react-immutable-proptypes' import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component' 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 { me } from '../initial_state'
import { saveShownOnboarding } from '../actions/onboarding' import { saveShownOnboarding } from '../actions/onboarding'
import { fetchGroups } from '../actions/groups' import { fetchGroups } from '../actions/groups'
import { saveUserProfileInformation } from '../actions/user'
import { import {
changeCompose, changeCompose,
clearCompose, clearCompose,
} from '../actions/compose' } from '../actions/compose'
import { makeGetAccount } from '../selectors' import { makeGetAccount } from '../selectors'
import Button from '../components/button' import Button from '../components/button'
import DisplayName from '../components/display_name'
import Divider from '../components/divider' import Divider from '../components/divider'
import FileInput from '../components/file_input' import FileInput from '../components/file_input'
import GroupListItem from '../components/group_list_item' import GroupListItem from '../components/group_list_item'
@ -21,12 +26,15 @@ import Image from '../components/image'
import Input from '../components/input' import Input from '../components/input'
import Text from '../components/text' import Text from '../components/text'
import ComposeFormContainer from './compose/containers/compose_form_container' import ComposeFormContainer from './compose/containers/compose_form_container'
import Responsive from './ui/util/responsive_component'
class SlideWelcome extends PureComponent { class SlideWelcome extends PureComponent {
render() { render() {
return ( return (
<div className={[_s.default, _s.width100PC, _s.height100PC].join(' ')}> <div className={[_s.default, _s.width100PC, _s.height100PC].join(' ')}>
<Image src='/headers/onboarding.png' alt='Welcome to Gab' />
<div className={[_s.default, _s.px15, _s.py15].join(' ')}> <div className={[_s.default, _s.px15, _s.py15].join(' ')}>
<Text size='large'>Gab is the home of free speech online and a place where users shape their own experience. </Text> <Text size='large'>Gab is the home of free speech online and a place where users shape their own experience. </Text>
@ -41,10 +49,6 @@ class SlideWelcome extends PureComponent {
</div> </div>
<Image
className={[_s.mtAuto].join(' ')}
src='https://gab.com/system/media_attachments/files/008/707/779/original/42de809171745057.png?1568251173'
/>
</div> </div>
) )
} }
@ -57,35 +61,72 @@ class SlidePhotos extends ImmutablePureComponent {
account: ImmutablePropTypes.map.isRequired, 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() { render() {
const { account } = this.props const { displayNameValue } = this.state
return ( return (
<div className={[_s.default, _s.width100PC].join(' ')}> <div className={[_s.default, _s.width100PC].join(' ')}>
<div className={[_s.default, _s.px15, _s.py15, _s.alignItemsCenter].join(' ')}> <div className={[_s.default, _s.px15, _s.py15, _s.alignItemsCenter].join(' ')}>
<div className={[_s.default, _s.py10, _s.width330PX].join(' ')}> <div className={[_s.default, _s.py10, _s.maxWidth640PX].join(' ')}>
<Text size='large' align='center'>Set your cover photo, profile photo and enter your display name so people can find you.</Text> <Text size='large' align='center'>Set your cover photo, profile photo and enter your display name so people can find you.</Text>
</div> </div>
<div className={[_s.default, _s.mt15, _s.width100PC, _s.alignItemsCenter].join(' ')}> <div className={[_s.default, _s.mt15, _s.width100PC, _s.alignItemsCenter].join(' ')}>
<div className={[_s.default, _s.width330PX, _s.border1PX, _s.borderColorSecondary, _s.overflowHidden, _s.radiusSmall].join(' ')}> <div className={[_s.default, _s.border1PX, _s.borderColorSecondary, _s.overflowHidden, _s.radiusSmall, _s.bgPrimary].join(' ')}>
<Image <FileInput
width={330} width='300px'
height={194} height='140px'
src='http://localhost:3000/system/accounts/headers/000/000/001/original/0a49fe388d16f372.jpg?1562898139' id='cover-photo'
onChange={this.handleCoverPhotoChange}
/> />
<div className={[_s.default, _s.mtNeg75PX, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}> <div className={[_s.default, _s.mtNeg32PX, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
<Image <FileInput
width={142} width='124px'
height={142} height='124px'
className={[_s.circle, _s.border6PX, _s.borderColorWhite].join(' ')} id='profile-photo'
src='http://localhost:3000/system/accounts/avatars/000/000/001/original/260e8c96c97834da.jpeg?1562898139' className={[_s.circle, _s.border6PX, _s.borderColorWhite, _s.bgPrimary].join(' ')}
onChange={this.handleProfilePhotoChange}
/> />
</div> </div>
<div className={[_s.default, _s.py5, _s.px15, _s.mt5, _s.mb15].join(' ')}> <div className={[_s.default, _s.py5, _s.px15, _s.mt5, _s.mb15].join(' ')}>
<Input <Input
placeholder='John Doe' id='display-name'
title='Display name'
placeholder='Add your name...'
value={displayNameValue}
onChange={this.handleDisplayNameChange}
onBlur={this.handleDisplayNameBlur}
/> />
</div> </div>
</div> </div>
@ -119,6 +160,7 @@ class SlideGroups extends ImmutablePureComponent {
groupIds.map((groupId, i) => ( groupIds.map((groupId, i) => (
<GroupListItem <GroupListItem
isAddable isAddable
isStatic
key={`group-collection-item-${i}`} key={`group-collection-item-${i}`}
id={groupId} id={groupId}
isLast={groupIds.count() - 1 === i} isLast={groupIds.count() - 1 === i}
@ -136,21 +178,49 @@ class SlideGroups extends ImmutablePureComponent {
class SlideFirstPost extends PureComponent { class SlideFirstPost extends PureComponent {
static propTypes = { static propTypes = {
submitted: PropTypes.bool.isRequired,
onNext: PropTypes.func.isRequired, onNext: PropTypes.func.isRequired,
} }
render() { render() {
const { submitted } = this.props
return ( return (
<div className={[_s.default, _s.width100PC].join(' ')}> <div className={[_s.default, _s.width100PC].join(' ')}>
<div className={[_s.default, _s.py15, _s.px15].join(' ')}> <div className={[_s.default, _s.py15, _s.px15].join(' ')}>
{
!submitted &&
<Fragment>
<Text size='large' className={_s.pb10}>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?</Text> <Text size='large' className={_s.pb10}>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?</Text>
<br /> <br />
<Divider /> <Divider />
<div className={[_s.default, _s.mt15, _s.boxShadowBlock, _s.radiusSmall].join(' ')}> <div className={[_s.default, _s.mt15, _s.boxShadowBlock, _s.radiusSmall].join(' ')}>
<ComposeFormContainer hidePro autoFocus /> <ComposeFormContainer
groupId={GAB_COM_INTRODUCE_YOURSELF_GROUP_ID}
hidePro
autoFocus
autoJoinGroup
/>
</div> </div>
</Fragment>
}
{
submitted &&
<Fragment>
<Text size='large' align='center'>Your gab was posted!</Text>
<br />
<Text size='large' align='center'>Click the checkbox in the top right to go to your home page.</Text>
<br />
<Button
href='/home'
onClick={this.props.onNext}
>
Finish
</Button>
</Fragment>
}
</div> </div>
</div> </div>
@ -163,13 +233,20 @@ const mapStateToProps = (state) => ({
account: makeGetAccount()(state, me), account: makeGetAccount()(state, me),
groupIds: state.getIn(['group_lists', 'featured', 'items']), groupIds: state.getIn(['group_lists', 'featured', 'items']),
shownOnboarding: state.getIn(['settings', 'shownOnboarding'], false), shownOnboarding: state.getIn(['settings', 'shownOnboarding'], false),
isSubmitting: state.getIn(['compose', 'is_submitting']),
}) })
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
onClearCompose: () => dispatch(clearCompose()), onClearCompose: () => dispatch(clearCompose()),
onSaveShownOnboarding: () => dispatch(saveShownOnboarding()), onSaveShownOnboarding: () => dispatch(saveShownOnboarding()),
onFetchFeaturedGroups: () => dispatch(fetchGroups('featured')), onFetchFeaturedGroups: () => dispatch(fetchGroups('featured')),
setPlaceholderCompose: () => dispatch(changeCompose('Hello everyone, I just joined Gab! Im looking forward to speaking freely and meeting new friends.')) setPlaceholderCompose() {
const defaultMessage = 'Hello everyone, I just joined Gab! Im looking forward to speaking freely and meeting new friends.'
dispatch(changeCompose(defaultMessage))
},
onSaveUserProfileInformation(data) {
dispatch(saveUserProfileInformation(data))
},
}) })
export default export default
@ -178,26 +255,32 @@ class Introduction extends ImmutablePureComponent {
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map.isRequired, account: ImmutablePropTypes.map.isRequired,
dispatch: PropTypes.func.isRequired,
groupIds: ImmutablePropTypes.list, groupIds: ImmutablePropTypes.list,
isSubmitting: PropTypes.bool.isRequired,
shownOnboarding: PropTypes.bool.isRequired, shownOnboarding: PropTypes.bool.isRequired,
setPlaceholderCompose: PropTypes.func.isRequired, setPlaceholderCompose: PropTypes.func.isRequired,
onClearCompose: PropTypes.func.isRequired, onClearCompose: PropTypes.func.isRequired,
onSaveShownOnboarding: PropTypes.func.isRequired, onSaveShownOnboarding: PropTypes.func.isRequired,
onFetchFeaturedGroups: PropTypes.func.isRequired, onFetchFeaturedGroups: PropTypes.func.isRequired,
onSaveUserProfileInformation: PropTypes.func.isRequired,
} }
state = { state = {
currentIndex: 0, currentIndex: 0,
submittedFirstPost: false,
} }
componentDidMount() { componentDidMount() {
if (!this.props.shownOnboarding) {
window.addEventListener('keyup', this.handleKeyUp) window.addEventListener('keyup', this.handleKeyUp)
this.props.onFetchFeaturedGroups() this.props.onFetchFeaturedGroups()
this.props.setPlaceholderCompose() this.props.setPlaceholderCompose()
this.props.onSaveShownOnboarding() this.props.onSaveShownOnboarding()
} }
componentDidUpdate(prevProps) {
if (!this.state.submittedFirstPost && !prevProps.isSubmitting && this.props.isSubmitting) {
this.setState({ submittedFirstPost: true })
}
} }
componentWillUnmount() { componentWillUnmount() {
@ -223,7 +306,7 @@ class Introduction extends ImmutablePureComponent {
currentIndex: newIndex, currentIndex: newIndex,
}) })
if (newIndex === 3) { if (newIndex === 4) {
this.props.onClearCompose() this.props.onClearCompose()
} }
} }
@ -243,15 +326,25 @@ class Introduction extends ImmutablePureComponent {
} }
} }
handleOnSaveUserProfileInformation = (data) => {
this.props.onSaveUserProfileInformation(data)
}
render() { render() {
const { account, groupIds } = this.props const { account, groupIds } = this.props
const { currentIndex } = this.state const { currentIndex, submittedFirstPost } = this.state
const pages = [ const pages = [
<SlideWelcome />, <SlideWelcome />,
<SlidePhotos account={account} />, <SlidePhotos
account={account}
onSave={this.handleOnSaveUserProfileInformation}
/>,
<SlideGroups groupIds={groupIds} />, <SlideGroups groupIds={groupIds} />,
<SlideFirstPost />, <SlideFirstPost
submitted={submittedFirstPost}
onNext={this.handleNext}
/>,
] ]
const titles = [ const titles = [
@ -284,20 +377,34 @@ class Introduction extends ImmutablePureComponent {
return ( return (
<div className={[_s.default, _s.width100PC, _s.heightMax80VH].join(' ')}> <div className={[_s.default, _s.width100PC, _s.heightMax80VH].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.justifyContentCenter, _s.borderBottom1PX, _s.borderColorSecondary, _s.height53PX, _s.px15].join(' ')}> <div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.justifyContentCenter, _s.borderBottom1PX, _s.borderColorSecondary, _s.height53PX, _s.px15].join(' ')}>
<Responsive min={BREAKPOINT_EXTRA_SMALL}>
<Heading> <Heading>
{title} {title}
</Heading> </Heading>
</Responsive>
<Responsive max={BREAKPOINT_EXTRA_SMALL}>
<Heading size='h2'>
{title}
</Heading>
</Responsive>
<div className={[_s.mlAuto].join(' ')}> <div className={[_s.mlAuto].join(' ')}>
<Button <Button
to={currentIndex === 3 ? '/home' : undefined} href={currentIndex === 3 ? '/home' : undefined}
onClick={this.handleNext} onClick={this.handleNext}
className={currentIndex !== 3 ? _s.px10 : undefined} className={_s.px10}
icon={currentIndex !== 3 ? 'arrow-right' : undefined} icon={currentIndex !== 3 ? 'arrow-right' : undefined}
iconSize={currentIndex !== 3 ? '18px' : undefined} iconSize={currentIndex !== 3 ? '18px' : undefined}
> >
{ {
currentIndex === 3 && currentIndex === 3 &&
<Text color='white' className={[_s.pr5]}>Complete</Text> <Fragment>
<Responsive min={BREAKPOINT_EXTRA_SMALL}>
<Text color='white' className={_s.px5}>Complete</Text>
</Responsive>
<Responsive max={BREAKPOINT_EXTRA_SMALL}>
<Icon id='check' size='14px' className={_s.fillWhite} />
</Responsive>
</Fragment>
} }
</Button> </Button>
</div> </div>
@ -341,7 +448,7 @@ class Introduction extends ImmutablePureComponent {
{pagination} {pagination}
</ul> </ul>
<Button <Button
to={currentIndex === 3 ? '/home' : undefined} href={currentIndex === 3 ? '/home' : undefined}
className={[_s.default, _s.width50PX, _s.mlAuto, _s.opacity05].join(' ')} className={[_s.default, _s.width50PX, _s.mlAuto, _s.opacity05].join(' ')}
onClick={this.handleNext} onClick={this.handleNext}
icon={currentIndex === 3 ? 'check' : 'arrow-right'} icon={currentIndex === 3 ? 'check' : 'arrow-right'}

View File

@ -1,20 +1,34 @@
import Block from '../components/block' import Block from '../components/block'
import NavigationBar from '../components/navigation_bar' import Icon from '../components/icon'
import BundleColumnError from '../components/bundle_column_error'
import Bundle from '../features/ui/util/bundle'
import { Introduction } from '../features/ui/util/async_components'
export default class IntroductionLayout extends PureComponent { export default class IntroductionLayout extends PureComponent {
static propTypes = { renderError = (props) => {
children: PropTypes.node, return <BundleColumnError {...props} />
title: PropTypes.string,
} }
render() { render() {
const { children, title } = this.props
return ( return (
<div className={[_s.default, _s.width100PC, _s.heightMin100VH, _s.bgTertiary].join(' ')}> <div className={[_s.default, _s.width100PC, _s.heightMin100VH, _s.bgTertiary].join(' ')}>
<NavigationBar title={title} noSearch noActions /> <div className={[_s.default, _s.z4, _s.heightMin53PX, _s.width100PC].join(' ')}>
<div className={[_s.default, _s.heightMin53PX, _s.bgNavigation, _s.alignItemsCenter, _s.z3, _s.top0, _s.right0, _s.left0, _s.posFixed].join(' ')} >
<div className={[_s.default, _s.saveAreaInsetPT, _s.saveAreaInsetPL, _s.saveAreaInsetPR, _s.flexRow, _s.width1255PX].join(' ')}>
<div className={[_s.default, _s.flexRow].join(' ')}>
<h1 className={[_s.default, _s.mr15].join(' ')}>
<div className={[_s.default, _s.justifyContentCenter, _s.noSelect, _s.noUnderline, _s.height53PX, _s.px10, _s.mr15].join(' ')}>
<Icon id='logo' className={_s.fillNavigationBrand} />
</div>
</h1>
</div>
</div>
</div>
</div>
<div className={[_s.default, _s.flexRow, _s.width100PC].join(' ')}> <div className={[_s.default, _s.flexRow, _s.width100PC].join(' ')}>
<div className={[_s.default, _s.width100PC].join(' ')}> <div className={[_s.default, _s.width100PC].join(' ')}>
@ -23,7 +37,9 @@ export default class IntroductionLayout extends PureComponent {
<div className={[_s.default, _s.width645PX, _s.maxWidth100PC42PX].join(' ')}> <div className={[_s.default, _s.width645PX, _s.maxWidth100PC42PX].join(' ')}>
<Block> <Block>
{children} <Bundle fetchComponent={Introduction} error={this.renderError}>
{Component => (<Component />)}
</Bundle>
</Block> </Block>
</div> </div>

View File

@ -8,7 +8,7 @@ import uuid from '../utils/uuid'
const initialState = ImmutableMap({ const initialState = ImmutableMap({
saved: true, saved: true,
onboarded: false, shownOnboarding: false,
skinTone: 1, skinTone: 1,
commentSorting: COMMENT_SORTING_TYPE_OLDEST, commentSorting: COMMENT_SORTING_TYPE_OLDEST,

View File

@ -28,6 +28,7 @@ class PostStatusService < BaseService
@text = @options[:text] || '' @text = @options[:text] || ''
@markdown = @options[:markdown] if @account.is_pro @markdown = @options[:markdown] if @account.is_pro
@in_reply_to = @options[:thread] @in_reply_to = @options[:thread]
@autoJoinGroup = @options[:autoJoinGroup] || false
return idempotency_duplicate if idempotency_given? && idempotency_duplicate? return idempotency_duplicate if idempotency_given? && idempotency_duplicate?
@ -104,6 +105,7 @@ class PostStatusService < BaseService
group_id = @options[:group_id] group_id = @options[:group_id]
return if group_id.blank? return if group_id.blank?
return if @autoJoinGroup
raise GabSocial::ValidationError, I18n.t('statuses.not_a_member_of_group') if not GroupAccount.where(account: @account, group_id: group_id).exists? raise GabSocial::ValidationError, I18n.t('statuses.not_a_member_of_group') if not GroupAccount.where(account: @account, group_id: group_id).exists?
end end