Added welcome page/introduction/onboarding flow
• Added: - welcome page/introduction/onboarding flow • Todo: - clean up code for showing new user the page - add code for saving profile, cover photos, display name from intro slides
This commit is contained in:
parent
81433b49fc
commit
e9475219c9
@ -1,8 +1,9 @@
|
|||||||
import { changeSetting, saveSettings } from './settings';
|
import moment from 'moment-mini'
|
||||||
|
import { changeSetting, saveSettings } from './settings'
|
||||||
|
|
||||||
export const INTRODUCTION_VERSION = 20181216044202;
|
export const MIN_ACCOUNT_CREATED_AT_ONBOARDING = moment('2020-07-14').valueOf()
|
||||||
|
|
||||||
export const closeOnboarding = () => dispatch => {
|
export const saveShownOnboarding = () => (dispatch) => {
|
||||||
dispatch(changeSetting(['introductionVersion'], INTRODUCTION_VERSION));
|
dispatch(changeSetting(['shownOnboarding'], true))
|
||||||
dispatch(saveSettings());
|
dispatch(saveSettings())
|
||||||
};
|
}
|
@ -1,234 +1,357 @@
|
|||||||
import ReactSwipeableViews from 'react-swipeable-views';
|
import ReactSwipeableViews from 'react-swipeable-views'
|
||||||
import classNames from 'classnames';
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
import { FormattedMessage } from 'react-intl';
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
import { closeOnboarding } from '../actions/onboarding';
|
import { CX } from '../constants'
|
||||||
|
import { me } from '../initial_state'
|
||||||
|
import { saveShownOnboarding } from '../actions/onboarding'
|
||||||
|
import { fetchGroups } from '../actions/groups'
|
||||||
|
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'
|
||||||
|
import Heading from '../components/heading'
|
||||||
|
import Icon from '../components/icon'
|
||||||
|
import Image from '../components/image'
|
||||||
|
import Input from '../components/input'
|
||||||
|
import Text from '../components/text'
|
||||||
|
import ComposeFormContainer from './compose/containers/compose_form_container'
|
||||||
|
|
||||||
// : todo :
|
class SlideWelcome extends PureComponent {
|
||||||
|
|
||||||
class FrameWelcome extends React.Component {
|
|
||||||
static propTypes = {
|
|
||||||
domain: PropTypes.string.isRequired,
|
|
||||||
onNext: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps) {
|
|
||||||
return nextProps.domain !== this.props.domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { domain, onNext } = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='introduction__frame'>
|
<div className={[_s.default, _s.width100PC, _s.height100PC].join(' ')}>
|
||||||
<div className='introduction__text introduction__text--centered'>
|
<div className={[_s.default, _s.px15, _s.py15].join(' ')}>
|
||||||
<h3>
|
|
||||||
<FormattedMessage id='introduction.welcome.headline' defaultMessage='First steps' />
|
<Text size='large'>Gab is the home of free speech online and a place where users shape their own experience. </Text>
|
||||||
</h3>
|
<br />
|
||||||
<p>
|
<Text size='large'>You will discover many different ideas, people, and topics on Gab.</Text>
|
||||||
<FormattedMessage
|
<br />
|
||||||
id='introduction.welcome.text'
|
<Text size='large'>Follow the people you find interesting and block or mute people you don't want to associate with.</Text>
|
||||||
defaultMessage="Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name."
|
<br />
|
||||||
values={{
|
<Text size='large'>Speak freely, associate freely!</Text>
|
||||||
domain: <code>{domain}</code>
|
<br />
|
||||||
}}
|
<Text size='large'>Let's get started!</Text>
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='introduction__action'>
|
<Image
|
||||||
<button className='button' onClick={onNext}>
|
className={[_s.mtAuto].join(' ')}
|
||||||
<FormattedMessage id='introduction.welcome.action' defaultMessage="Let's go!" />
|
src='https://gab.com/system/media_attachments/files/008/707/779/original/42de809171745057.png?1568251173'
|
||||||
</button>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class FrameFederation extends React.Component {
|
class SlidePhotos extends ImmutablePureComponent {
|
||||||
static propTypes = {
|
|
||||||
onNext: PropTypes.func.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate() {
|
static propTypes = {
|
||||||
return false;
|
account: ImmutablePropTypes.map.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
const { account } = this.props
|
||||||
<div className='introduction__frame'>
|
|
||||||
<div className='introduction__text introduction__text--columnized'>
|
return (
|
||||||
<div>
|
<div className={[_s.default, _s.width100PC].join(' ')}>
|
||||||
<h3>
|
<div className={[_s.default, _s.px15, _s.py15, _s.alignItemsCenter].join(' ')}>
|
||||||
<FormattedMessage id='introduction.federation.home.headline' defaultMessage='Home' />
|
|
||||||
</h3>
|
<div className={[_s.default, _s.py10, _s.width330PX].join(' ')}>
|
||||||
<p>
|
<Text size='large' align='center'>Set your cover photo, profile photo and enter your display name so people can find you.</Text>
|
||||||
<FormattedMessage
|
</div>
|
||||||
id='introduction.federation.home.text'
|
|
||||||
defaultMessage='Posts from people you follow will appear in your home feed. You can follow anyone on any server!'
|
<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(' ')}>
|
||||||
</p>
|
<Image
|
||||||
</div>
|
width={330}
|
||||||
</div>
|
height={194}
|
||||||
|
src='http://localhost:3000/system/accounts/headers/000/000/001/original/0a49fe388d16f372.jpg?1562898139'
|
||||||
|
/>
|
||||||
|
<div className={[_s.default, _s.mtNeg75PX, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
|
||||||
|
<Image
|
||||||
|
width={142}
|
||||||
|
height={142}
|
||||||
|
className={[_s.circle, _s.border6PX, _s.borderColorWhite].join(' ')}
|
||||||
|
src='http://localhost:3000/system/accounts/avatars/000/000/001/original/260e8c96c97834da.jpeg?1562898139'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={[_s.default, _s.py5, _s.px15, _s.mt5, _s.mb15].join(' ')}>
|
||||||
|
<Input
|
||||||
|
placeholder='John Doe'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='introduction__action'>
|
|
||||||
<button className='button' onClick={onNext}>
|
|
||||||
<FormattedMessage id='introduction.federation.action' defaultMessage='Next' />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
class FrameInteractions extends React.Component {
|
}
|
||||||
|
|
||||||
|
class SlideGroups extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
groupIds: ImmutablePropTypes.list,
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { groupIds } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={[_s.default, _s.width100PC].join(' ')}>
|
||||||
|
<div className={[_s.default, _s.py15, _s.alignItemsCenter].join(' ')}>
|
||||||
|
<div className={[_s.default, _s.px15, _s.mb15].join(' ')}>
|
||||||
|
<Text size='large'>Gab Groups are a great way to connect with people who share your interests. Please select a few groups to get started.</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={[_s.default, _s.width100PC].join(' ')}>
|
||||||
|
{
|
||||||
|
groupIds.map((groupId, i) => (
|
||||||
|
<GroupListItem
|
||||||
|
isAddable
|
||||||
|
key={`group-collection-item-${i}`}
|
||||||
|
id={groupId}
|
||||||
|
isLast={groupIds.count() - 1 === i}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SlideFirstPost extends PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onNext: PropTypes.func.isRequired,
|
onNext: PropTypes.func.isRequired,
|
||||||
};
|
|
||||||
|
|
||||||
shouldComponentUpdate() {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className='introduction__frame'>
|
<div className={[_s.default, _s.width100PC].join(' ')}>
|
||||||
<div className='introduction__text introduction__text--columnized'>
|
<div className={[_s.default, _s.py15, _s.px15].join(' ')}>
|
||||||
<div>
|
<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>
|
||||||
<h3>
|
<br />
|
||||||
<FormattedMessage id='introduction.interactions.reply.headline' defaultMessage='Reply' />
|
|
||||||
</h3>
|
<Divider />
|
||||||
<p>
|
|
||||||
<FormattedMessage
|
<div className={[_s.default, _s.mt15, _s.boxShadowBlock, _s.radiusSmall].join(' ')}>
|
||||||
id='introduction.interactions.reply.text'
|
<ComposeFormContainer hidePro autoFocus />
|
||||||
defaultMessage="You can reply to other people's and your own gabs, which will chain them together in a conversation."
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3>
|
|
||||||
<FormattedMessage id='introduction.interactions.repost.headline' defaultMessage='Repost' />
|
|
||||||
</h3>
|
|
||||||
<p>
|
|
||||||
<FormattedMessage id='introduction.interactions.repost.text' defaultMessage="You can share other people's gabs with your followers by reposting them." />
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3>
|
|
||||||
<FormattedMessage id='introduction.interactions.favorite.headline' defaultMessage='Favorite' />
|
|
||||||
</h3>
|
|
||||||
<p>
|
|
||||||
<FormattedMessage id='introduction.interactions.favorite.text' defaultMessage='You can save a gab for later, and let the author know that you liked it, by favoriting it.' />
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='introduction__action'>
|
|
||||||
<button className='button' onClick={onNext}>
|
|
||||||
<FormattedMessage id='introduction.interactions.action' defaultMessage='Finish tutorial!' />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({
|
||||||
|
account: makeGetAccount()(state, me),
|
||||||
|
groupIds: state.getIn(['group_lists', 'featured', 'items']),
|
||||||
|
shownOnboarding: state.getIn(['settings', 'shownOnboarding'], false),
|
||||||
|
})
|
||||||
|
|
||||||
|
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.'))
|
||||||
|
})
|
||||||
|
|
||||||
export default
|
export default
|
||||||
@connect(state => ({ domain: state.getIn(['meta', 'domain']) }))
|
@connect(mapStateToProps, mapDispatchToProps)
|
||||||
class Introduction extends PureComponent {
|
class Introduction extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
domain: PropTypes.string.isRequired,
|
account: ImmutablePropTypes.map.isRequired,
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
};
|
groupIds: ImmutablePropTypes.list,
|
||||||
|
shownOnboarding: PropTypes.bool.isRequired,
|
||||||
|
setPlaceholderCompose: PropTypes.func.isRequired,
|
||||||
|
onClearCompose: PropTypes.func.isRequired,
|
||||||
|
onSaveShownOnboarding: PropTypes.func.isRequired,
|
||||||
|
onFetchFeaturedGroups: PropTypes.func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
currentIndex: 0,
|
currentIndex: 0,
|
||||||
};
|
|
||||||
|
|
||||||
componentWillMount () {
|
|
||||||
this.pages = [
|
|
||||||
<FrameWelcome domain={this.props.domain} onNext={this.handleNext} />,
|
|
||||||
<FrameFederation onNext={this.handleNext} />,
|
|
||||||
<FrameInteractions onNext={this.handleFinish} />,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
window.addEventListener('keyup', this.handleKeyUp);
|
if (!this.props.shownOnboarding) {
|
||||||
|
window.addEventListener('keyup', this.handleKeyUp)
|
||||||
|
this.props.onFetchFeaturedGroups()
|
||||||
|
this.props.setPlaceholderCompose()
|
||||||
|
this.props.onSaveShownOnboarding()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
window.addEventListener('keyup', this.handleKeyUp);
|
window.addEventListener('keyup', this.handleKeyUp)
|
||||||
|
this.props.onClearCompose()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDot = (e) => {
|
handleDot = (e) => {
|
||||||
const i = Number(e.currentTarget.getAttribute('data-index'));
|
const i = Number(e.currentTarget.getAttribute('data-index'))
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
this.setState({ currentIndex: i });
|
this.setState({ currentIndex: i })
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePrev = () => {
|
handlePrev = () => {
|
||||||
this.setState(({ currentIndex }) => ({
|
this.setState(({ currentIndex }) => ({
|
||||||
currentIndex: Math.max(0, currentIndex - 1),
|
currentIndex: Math.max(0, currentIndex - 1),
|
||||||
}));
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNext = () => {
|
handleNext = () => {
|
||||||
const { pages } = this;
|
const newIndex = Math.min(this.state.currentIndex + 1, 3)
|
||||||
|
this.setState({
|
||||||
|
currentIndex: newIndex,
|
||||||
|
})
|
||||||
|
|
||||||
this.setState(({ currentIndex }) => ({
|
if (newIndex === 3) {
|
||||||
currentIndex: Math.min(currentIndex + 1, pages.length - 1),
|
this.props.onClearCompose()
|
||||||
}));
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSwipe = (index) => {
|
handleSwipe = (index) => {
|
||||||
this.setState({ currentIndex: index });
|
this.setState({ currentIndex: index })
|
||||||
}
|
|
||||||
|
|
||||||
handleFinish = () => {
|
|
||||||
this.props.dispatch(closeOnboarding());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyUp = ({ key }) => {
|
handleKeyUp = ({ key }) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'ArrowLeft':
|
case 'ArrowLeft':
|
||||||
this.handlePrev();
|
this.handlePrev()
|
||||||
break;
|
break
|
||||||
case 'ArrowRight':
|
case 'ArrowRight':
|
||||||
this.handleNext();
|
this.handleNext()
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { currentIndex } = this.state;
|
const { account, groupIds } = this.props
|
||||||
const { pages } = this;
|
const { currentIndex } = this.state
|
||||||
|
|
||||||
|
const pages = [
|
||||||
|
<SlideWelcome />,
|
||||||
|
<SlidePhotos account={account} />,
|
||||||
|
<SlideGroups groupIds={groupIds} />,
|
||||||
|
<SlideFirstPost />,
|
||||||
|
]
|
||||||
|
|
||||||
|
const titles = [
|
||||||
|
`Welcome to Gab!`,
|
||||||
|
'Complete your profile',
|
||||||
|
'Find your people',
|
||||||
|
'Start a conversation',
|
||||||
|
]
|
||||||
|
const title = titles[currentIndex]
|
||||||
|
|
||||||
|
const pagination = pages.map((page, i) => {
|
||||||
|
const btnClasses = CX({
|
||||||
|
default: 1,
|
||||||
|
width10PX: 1,
|
||||||
|
height10PX: 1,
|
||||||
|
outlineNone: 1,
|
||||||
|
circle: 1,
|
||||||
|
cursorPointer: 1,
|
||||||
|
bgBrandLightOpaque: i !== currentIndex,
|
||||||
|
bgBrand: i === currentIndex,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className={[_s.default, _s.px5].join(' ')} key={`intro-slide-${i}`}>
|
||||||
|
<button tabIndex='0' className={btnClasses} onClick={this.handleDot} data-index={i} />
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='introduction'>
|
<div className={[_s.default, _s.width100PC, _s.heightMax80VH].join(' ')}>
|
||||||
<ReactSwipeableViews index={currentIndex} onChangeIndex={this.handleSwipe} className='introduction__pager'>
|
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.justifyContentCenter, _s.borderBottom1PX, _s.borderColorSecondary, _s.height53PX, _s.px15].join(' ')}>
|
||||||
{pages.map((page, i) => (
|
<Heading>
|
||||||
<div key={i} className={classNames('introduction__frame-wrapper', { 'active': i === currentIndex })}>{page}</div>
|
{title}
|
||||||
))}
|
</Heading>
|
||||||
|
<div className={[_s.mlAuto].join(' ')}>
|
||||||
|
<Button
|
||||||
|
to={currentIndex === 3 ? '/home' : undefined}
|
||||||
|
onClick={this.handleNext}
|
||||||
|
className={currentIndex !== 3 ? _s.px10 : undefined}
|
||||||
|
icon={currentIndex !== 3 ? 'arrow-right' : undefined}
|
||||||
|
iconSize={currentIndex !== 3 ? '18px' : undefined}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
currentIndex === 3 &&
|
||||||
|
<Text color='white' className={[_s.pr5]}>Complete</Text>
|
||||||
|
}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ReactSwipeableViews
|
||||||
|
index={currentIndex}
|
||||||
|
onChangeIndex={this.handleSwipe}
|
||||||
|
className={[_s.default, _s.flexNormal, _s.heightCalc80VH106PX].join(' ')}
|
||||||
|
containerStyle={{
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
slideStyle={{
|
||||||
|
// height: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pages.map((page, i) => (
|
||||||
|
<div key={i} className={[_s.default, _s.heightCalc80VH106PX].join(' ')}>
|
||||||
|
{page}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
</ReactSwipeableViews>
|
</ReactSwipeableViews>
|
||||||
|
|
||||||
<div className='introduction__dots'>
|
<div className={[_s.default, _s.px15, _s.height53PX, _s.alignItemsCenter, _s.justifyContentCenter, _s.borderTop1PX, _s.borderColorSecondary, _s.width100PC, _s.flexRow].join(' ')}>
|
||||||
{pages.map((_, i) => (
|
<div className={[_s.default, _s.width50PX, _s.mrAuto].join(' ')}>
|
||||||
<div
|
{
|
||||||
key={`dot-${i}`}
|
currentIndex !== 0 &&
|
||||||
role='button'
|
<Button
|
||||||
tabIndex='0'
|
className={_s.opacity05}
|
||||||
data-index={i}
|
onClick={this.handlePrev}
|
||||||
onClick={this.handleDot}
|
icon='arrow-left'
|
||||||
className={classNames('introduction__dot', { active: i === currentIndex })}
|
backgroundColor='none'
|
||||||
/>
|
color='secondary'
|
||||||
))}
|
iconSize='20px'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<ul className={[_s.default, _s.height100PC, _s.flexGrow1, _s.alignItemsCenter, _s.justifyContentCenter, _s.flexRow, _s.listStyleNone].join(' ')}>
|
||||||
|
{pagination}
|
||||||
|
</ul>
|
||||||
|
<Button
|
||||||
|
to={currentIndex === 3 ? '/home' : undefined}
|
||||||
|
className={[_s.default, _s.width50PX, _s.mlAuto, _s.opacity05].join(' ')}
|
||||||
|
onClick={this.handleNext}
|
||||||
|
icon={currentIndex === 3 ? 'check' : 'arrow-right'}
|
||||||
|
backgroundColor='none'
|
||||||
|
color='secondary'
|
||||||
|
iconSize='20px'
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -5,6 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl'
|
|||||||
import { Switch, Redirect, withRouter } from 'react-router-dom'
|
import { Switch, Redirect, withRouter } from 'react-router-dom'
|
||||||
import debounce from 'lodash.debounce'
|
import debounce from 'lodash.debounce'
|
||||||
import queryString from 'query-string'
|
import queryString from 'query-string'
|
||||||
|
import moment from 'moment-mini'
|
||||||
import { uploadCompose, resetCompose } from '../../actions/compose'
|
import { uploadCompose, resetCompose } from '../../actions/compose'
|
||||||
import { expandHomeTimeline } from '../../actions/timelines'
|
import { expandHomeTimeline } from '../../actions/timelines'
|
||||||
import { fetchGroups } from '../../actions/groups'
|
import { fetchGroups } from '../../actions/groups'
|
||||||
@ -15,6 +16,7 @@ import {
|
|||||||
} from '../../actions/notifications'
|
} from '../../actions/notifications'
|
||||||
import LoadingBar from '../../components/loading_bar'
|
import LoadingBar from '../../components/loading_bar'
|
||||||
import { fetchFilters } from '../../actions/filters'
|
import { fetchFilters } from '../../actions/filters'
|
||||||
|
import { MIN_ACCOUNT_CREATED_AT_ONBOARDING } from '../../actions/onboarding'
|
||||||
import { clearHeight } from '../../actions/height_cache'
|
import { clearHeight } from '../../actions/height_cache'
|
||||||
import { openModal } from '../../actions/modal'
|
import { openModal } from '../../actions/modal'
|
||||||
import WrappedRoute from './util/wrapped_route'
|
import WrappedRoute from './util/wrapped_route'
|
||||||
@ -38,6 +40,7 @@ import ModalPage from '../../pages/modal_page'
|
|||||||
import SettingsPage from '../../pages/settings_page'
|
import SettingsPage from '../../pages/settings_page'
|
||||||
import ProPage from '../../pages/pro_page'
|
import ProPage from '../../pages/pro_page'
|
||||||
import ExplorePage from '../../pages/explore_page'
|
import ExplorePage from '../../pages/explore_page'
|
||||||
|
import IntroductionPage from '../../pages/introduction_page'
|
||||||
import AboutPage from '../../pages/about_page'
|
import AboutPage from '../../pages/about_page'
|
||||||
// import AuthPage from '../../pages/auth_page'
|
// import AuthPage from '../../pages/auth_page'
|
||||||
|
|
||||||
@ -56,7 +59,6 @@ import {
|
|||||||
Following,
|
Following,
|
||||||
FollowRequests,
|
FollowRequests,
|
||||||
GenericNotFound,
|
GenericNotFound,
|
||||||
GettingStarted,
|
|
||||||
GroupsCollection,
|
GroupsCollection,
|
||||||
GroupCreate,
|
GroupCreate,
|
||||||
GroupMembers,
|
GroupMembers,
|
||||||
@ -64,6 +66,7 @@ import {
|
|||||||
GroupTimeline,
|
GroupTimeline,
|
||||||
HashtagTimeline,
|
HashtagTimeline,
|
||||||
HomeTimeline,
|
HomeTimeline,
|
||||||
|
Introduction,
|
||||||
Investors,
|
Investors,
|
||||||
LikedStatuses,
|
LikedStatuses,
|
||||||
ListCreate,
|
ListCreate,
|
||||||
@ -94,6 +97,8 @@ const messages = defineMessages({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = (state) => ({
|
||||||
|
shownOnboarding: state.getIn(['settings', 'shownOnboarding'], false),
|
||||||
|
accountCreatedAt: state.getIn(['accounts', me, 'created_at']),
|
||||||
isComposing: state.getIn(['compose', 'is_composing']),
|
isComposing: state.getIn(['compose', 'is_composing']),
|
||||||
hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0,
|
hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0,
|
||||||
hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
|
hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||||
@ -129,6 +134,7 @@ class SwitchingArea extends PureComponent {
|
|||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
location: PropTypes.object,
|
location: PropTypes.object,
|
||||||
onLayoutChange: PropTypes.func.isRequired,
|
onLayoutChange: PropTypes.func.isRequired,
|
||||||
|
shownOnboarding: PropTypes.bool.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -145,8 +151,8 @@ class SwitchingArea extends PureComponent {
|
|||||||
// The cached heights are no longer accurate, invalidate
|
// The cached heights are no longer accurate, invalidate
|
||||||
this.props.onLayoutChange()
|
this.props.onLayoutChange()
|
||||||
}, 500, {
|
}, 500, {
|
||||||
trailing: true,
|
trailing: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
setRef = c => {
|
setRef = c => {
|
||||||
this.node = c.getWrappedInstance()
|
this.node = c.getWrappedInstance()
|
||||||
@ -160,6 +166,8 @@ class SwitchingArea extends PureComponent {
|
|||||||
<Redirect from='/' to='/home' exact />
|
<Redirect from='/' to='/home' exact />
|
||||||
<WrappedRoute path='/home' exact page={HomePage} component={HomeTimeline} content={children} />
|
<WrappedRoute path='/home' exact page={HomePage} component={HomeTimeline} content={children} />
|
||||||
|
|
||||||
|
<WrappedRoute path='/welcome' exact page={IntroductionPage} component={Introduction} content={children} />
|
||||||
|
|
||||||
<WrappedRoute path='/about' publicRoute exact page={AboutPage} component={About} content={children} componentParams={{ title: 'About' }} />
|
<WrappedRoute path='/about' publicRoute exact page={AboutPage} component={About} content={children} componentParams={{ title: 'About' }} />
|
||||||
<WrappedRoute path='/about/dmca' publicRoute exact page={AboutPage} component={DMCA} content={children} componentParams={{ title: 'DMCA' }} />
|
<WrappedRoute path='/about/dmca' publicRoute exact page={AboutPage} component={DMCA} content={children} componentParams={{ title: 'DMCA' }} />
|
||||||
<WrappedRoute path='/about/investors' publicRoute exact page={AboutPage} component={Investors} content={children} componentParams={{ title: 'Investors' }} />
|
<WrappedRoute path='/about/investors' publicRoute exact page={AboutPage} component={Investors} content={children} componentParams={{ title: 'Investors' }} />
|
||||||
@ -261,6 +269,8 @@ class UI extends PureComponent {
|
|||||||
hasMediaAttachments: PropTypes.bool,
|
hasMediaAttachments: PropTypes.bool,
|
||||||
location: PropTypes.object,
|
location: PropTypes.object,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
|
shownOnboarding: PropTypes.bool.isRequired,
|
||||||
|
accountCreatedAt: PropTypes.string.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -377,6 +387,15 @@ class UI extends PureComponent {
|
|||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
if (!me) return
|
if (!me) return
|
||||||
|
|
||||||
|
//If first time opening app, and is new user, show onboarding
|
||||||
|
const accountCreatedAtValue = moment(this.props.accountCreatedAt).valueOf()
|
||||||
|
const shouldShowOnboarding = !this.props.shownOnboarding && accountCreatedAtValue > MIN_ACCOUNT_CREATED_AT_ONBOARDING
|
||||||
|
|
||||||
|
if (shouldShowOnboarding) {
|
||||||
|
this.context.router.history.push('/welcome')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener('beforeunload', this.handleBeforeUnload, false)
|
window.addEventListener('beforeunload', this.handleBeforeUnload, false)
|
||||||
|
|
||||||
document.addEventListener('dragenter', this.handleDragEnter, false)
|
document.addEventListener('dragenter', this.handleDragEnter, false)
|
||||||
@ -415,6 +434,8 @@ class UI extends PureComponent {
|
|||||||
|
|
||||||
this.setState({ fetchedNotifications: true })
|
this.setState({ fetchedNotifications: true })
|
||||||
this.props.dispatch(expandNotifications())
|
this.props.dispatch(expandNotifications())
|
||||||
|
} else if (pathname === '/welcome') {
|
||||||
|
this.context.router.history.push('/home')
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.dispatch(initializeNotifications())
|
this.props.dispatch(initializeNotifications())
|
||||||
@ -531,8 +552,12 @@ class UI extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {
|
||||||
|
children,
|
||||||
|
location,
|
||||||
|
shownOnboarding,
|
||||||
|
} = this.props
|
||||||
const { draggingOver } = this.state
|
const { draggingOver } = this.state
|
||||||
const { children, location } = this.props
|
|
||||||
|
|
||||||
// : todo :
|
// : todo :
|
||||||
// const handlers = me ? {
|
// const handlers = me ? {
|
||||||
@ -555,7 +580,11 @@ class UI extends PureComponent {
|
|||||||
<div ref={this.setRef} className={_s.gabsocial}>
|
<div ref={this.setRef} className={_s.gabsocial}>
|
||||||
<LoadingBar className={[_s.height1PX, _s.z3, _s.bgBrandLight].join(' ')} />
|
<LoadingBar className={[_s.height1PX, _s.z3, _s.bgBrandLight].join(' ')} />
|
||||||
|
|
||||||
<SwitchingArea location={location} onLayoutChange={this.handleLayoutChange}>
|
<SwitchingArea
|
||||||
|
location={location}
|
||||||
|
onLayoutChange={this.handleLayoutChange}
|
||||||
|
shownOnboarding={shownOnboarding}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</SwitchingArea>
|
</SwitchingArea>
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ export function HashtagTimelineSettingsModal() { return import(/* webpackChunkNa
|
|||||||
export function HomeTimeline() { return import(/* webpackChunkName: "features/home_timeline" */'../../home_timeline') }
|
export function HomeTimeline() { return import(/* webpackChunkName: "features/home_timeline" */'../../home_timeline') }
|
||||||
export function HomeTimelineSettingsModal() { return import(/* webpackChunkName: "components/home_timeline_settings_modal" */'../../../components/modal/home_timeline_settings_modal') }
|
export function HomeTimelineSettingsModal() { return import(/* webpackChunkName: "components/home_timeline_settings_modal" */'../../../components/modal/home_timeline_settings_modal') }
|
||||||
export function HotkeysModal() { return import(/* webpackChunkName: "components/hotkeys_modal" */'../../../components/modal/hotkeys_modal') }
|
export function HotkeysModal() { return import(/* webpackChunkName: "components/hotkeys_modal" */'../../../components/modal/hotkeys_modal') }
|
||||||
|
export function Introduction() { return import(/* webpackChunkName: "features/introduction" */'../../introduction') }
|
||||||
export function Investors() { return import(/* webpackChunkName: "features/about/investors" */'../../about/investors') }
|
export function Investors() { return import(/* webpackChunkName: "features/about/investors" */'../../about/investors') }
|
||||||
export function ListAddUserModal() { return import(/* webpackChunkName: "features/list_add_user_modal" */'../../../components/modal/list_add_user_modal') }
|
export function ListAddUserModal() { return import(/* webpackChunkName: "features/list_add_user_modal" */'../../../components/modal/list_add_user_modal') }
|
||||||
export function ListCreate() { return import(/* webpackChunkName: "features/list_create" */'../../list_create') }
|
export function ListCreate() { return import(/* webpackChunkName: "features/list_create" */'../../list_create') }
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import Block from '../components/block'
|
||||||
import NavigationBar from '../components/navigation_bar'
|
import NavigationBar from '../components/navigation_bar'
|
||||||
|
|
||||||
export default class IntroductionLayout extends PureComponent {
|
export default class IntroductionLayout extends PureComponent {
|
||||||
@ -13,13 +14,19 @@ export default class IntroductionLayout extends PureComponent {
|
|||||||
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} />
|
<NavigationBar title={title} noSearch noActions />
|
||||||
|
|
||||||
<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(' ')}>
|
||||||
<main role='main'>
|
<main role='main'>
|
||||||
<div className={[_s.default, _s.mlAuto, _s.mrAuto].join(' ')}>
|
<div className={[_s.default, _s.alignItemsCenter, _s.py15, _s.px15, _s.mlAuto, _s.mrAuto].join(' ')}>
|
||||||
{children}
|
|
||||||
|
<div className={[_s.default, _s.width645PX, _s.maxWidth100PC42PX].join(' ')}>
|
||||||
|
<Block>
|
||||||
|
{children}
|
||||||
|
</Block>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
@ -483,6 +483,7 @@ pre {
|
|||||||
.heightMax200PX { max-height: 200px; }
|
.heightMax200PX { max-height: 200px; }
|
||||||
.heightMax56PX { max-height: 56px; }
|
.heightMax56PX { max-height: 56px; }
|
||||||
.heightCalc53PX { height: calc(100vh - 53px); }
|
.heightCalc53PX { height: calc(100vh - 53px); }
|
||||||
|
.heightCalc80VH106PX { height: calc(80vh - 106px); }
|
||||||
|
|
||||||
.heightMin100VH { min-height: 100vh; }
|
.heightMin100VH { min-height: 100vh; }
|
||||||
.heightMin50VH { min-height: 50vh; }
|
.heightMin50VH { min-height: 50vh; }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user