433 lines
13 KiB
JavaScript
433 lines
13 KiB
JavaScript
import React from 'react'
|
|
import PropTypes from 'prop-types'
|
|
import { connect } from 'react-redux'
|
|
import ReactSwipeableViews from 'react-swipeable-views'
|
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
|
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 { makeGetAccount } from '../selectors'
|
|
import Button from '../components/button'
|
|
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 Pagination from '../components/pagination'
|
|
import ComposeFormContainer from './compose/containers/compose_form_container'
|
|
import Responsive from './ui/util/responsive_component'
|
|
|
|
const SlideWelcome = () => (
|
|
<div className={[_s.d, _s.w100PC, _s.h100PC].join(' ')}>
|
|
<Image src='/headers/onboarding.png' alt='Welcome to Gab' />
|
|
|
|
<div className={[_s.d, _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>
|
|
<br />
|
|
<Text size='large'>You will discover many different ideas, people, and topics on Gab.</Text>
|
|
<br />
|
|
<Text size='large'>Follow the people you find interesting and block or mute people you don't want to associate with.</Text>
|
|
<br />
|
|
<Text size='large'>Speak freely, associate freely!</Text>
|
|
<br />
|
|
<Text size='large'>Let's get started!</Text>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
)
|
|
|
|
class SlidePhotos extends ImmutablePureComponent {
|
|
|
|
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 { displayNameValue } = this.state
|
|
|
|
return (
|
|
<div className={[_s.d, _s.w100PC].join(' ')}>
|
|
<div className={[_s.d, _s.px15, _s.py15, _s.aiCenter].join(' ')}>
|
|
|
|
<div className={[_s.d, _s.py10, _s.maxW640PX].join(' ')}>
|
|
<Text size='large' align='center'>Set your cover photo, profile photo and enter your display name so people can find you.</Text>
|
|
</div>
|
|
|
|
<div className={[_s.d, _s.mt15, _s.w100PC, _s.aiCenter].join(' ')}>
|
|
<div className={[_s.d, _s.border1PX, _s.borderColorSecondary, _s.overflowHidden, _s.radiusSmall, _s.bgPrimary].join(' ')}>
|
|
<FileInput
|
|
width='300px'
|
|
height='140px'
|
|
id='cover-photo'
|
|
onChange={this.handleCoverPhotoChange}
|
|
/>
|
|
<div className={[_s.d, _s.mtNeg32PX, _s.aiCenter, _s.jcCenter].join(' ')}>
|
|
<FileInput
|
|
width='124px'
|
|
height='124px'
|
|
id='profile-photo'
|
|
className={[_s.circle, _s.border6PX, _s.borderColorWhite, _s.bgPrimary].join(' ')}
|
|
onChange={this.handleProfilePhotoChange}
|
|
/>
|
|
</div>
|
|
<div className={[_s.d, _s.py5, _s.px15, _s.mt5, _s.mb15].join(' ')}>
|
|
<Input
|
|
id='display-name'
|
|
title='Display name'
|
|
placeholder='Add your name...'
|
|
value={displayNameValue}
|
|
onChange={this.handleDisplayNameChange}
|
|
onBlur={this.handleDisplayNameBlur}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
}
|
|
|
|
SlidePhotos.propTypes = {
|
|
account: ImmutablePropTypes.map.isRequired,
|
|
}
|
|
|
|
class SlideGroups extends ImmutablePureComponent {
|
|
|
|
render() {
|
|
const { groupIds } = this.props
|
|
|
|
return (
|
|
<div className={[_s.d, _s.w100PC].join(' ')}>
|
|
<div className={[_s.d, _s.py15, _s.aiCenter].join(' ')}>
|
|
<div className={[_s.d, _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.d, _s.w100PC].join(' ')}>
|
|
{
|
|
groupIds.map((groupId, i) => (
|
|
<GroupListItem
|
|
isAddable
|
|
isStatic
|
|
key={`group-collection-item-${i}`}
|
|
id={groupId}
|
|
isLast={groupIds.count() - 1 === i}
|
|
/>
|
|
))
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
}
|
|
|
|
SlideGroups.propTypes = {
|
|
groupIds: ImmutablePropTypes.list,
|
|
}
|
|
|
|
class SlideFirstPost extends React.PureComponent {
|
|
|
|
render() {
|
|
const { submitted } = this.props
|
|
|
|
return (
|
|
<div className={[_s.d, _s.w100PC].join(' ')}>
|
|
<div className={[_s.d, _s.py15, _s.px15].join(' ')}>
|
|
{
|
|
!submitted &&
|
|
<React.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>
|
|
<br />
|
|
|
|
<Divider />
|
|
|
|
<div className={[_s.d, _s.mt15, _s.boxShadowBlock, _s.radiusSmall].join(' ')}>
|
|
<ComposeFormContainer
|
|
groupId={GAB_COM_INTRODUCE_YOURSELF_GROUP_ID}
|
|
hidePro
|
|
autoFocus
|
|
autoJoinGroup
|
|
/>
|
|
</div>
|
|
</React.Fragment>
|
|
}
|
|
{
|
|
submitted &&
|
|
<React.Fragment>
|
|
<Text size='large' align='center'>Your gab was posted!</Text>
|
|
<br />
|
|
<Text size='large' align='center'>Welcome to our community, remember to speak freely.</Text>
|
|
<br />
|
|
<Button
|
|
href='/home'
|
|
onClick={this.props.onNext}
|
|
>
|
|
Finish
|
|
</Button>
|
|
</React.Fragment>
|
|
}
|
|
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
}
|
|
|
|
SlideFirstPost.propTypes = {
|
|
submitted: PropTypes.bool.isRequired,
|
|
onNext: PropTypes.func.isRequired,
|
|
}
|
|
|
|
class Introduction extends ImmutablePureComponent {
|
|
|
|
state = {
|
|
currentIndex: 0,
|
|
submittedFirstPost: false,
|
|
}
|
|
|
|
componentDidMount() {
|
|
window.addEventListener('keyup', this.handleKeyUp)
|
|
this.props.onFetchFeaturedGroups()
|
|
this.props.onSaveShownOnboarding()
|
|
}
|
|
|
|
componentDidUpdate(prevProps) {
|
|
if (!this.state.submittedFirstPost && !prevProps.isSubmitting && this.props.isSubmitting) {
|
|
this.setState({ submittedFirstPost: true })
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
window.addEventListener('keyup', this.handleKeyUp)
|
|
}
|
|
|
|
handleDot = (i) => {
|
|
this.setState({ currentIndex: i })
|
|
}
|
|
|
|
handlePrev = () => {
|
|
this.setState(({ currentIndex }) => ({
|
|
currentIndex: Math.max(0, currentIndex - 1),
|
|
}))
|
|
}
|
|
|
|
handleNext = () => {
|
|
const newIndex = Math.min(this.state.currentIndex + 1, 3)
|
|
this.setState({
|
|
currentIndex: newIndex,
|
|
})
|
|
}
|
|
|
|
handleSwipe = (index) => {
|
|
this.setState({ currentIndex: index })
|
|
}
|
|
|
|
handleKeyUp = ({ key }) => {
|
|
switch (key) {
|
|
case 'ArrowLeft':
|
|
this.handlePrev()
|
|
break
|
|
case 'ArrowRight':
|
|
this.handleNext()
|
|
break
|
|
}
|
|
}
|
|
|
|
handleOnSaveUserProfileInformation = (data) => {
|
|
this.props.onSaveUserProfileInformation(data)
|
|
}
|
|
|
|
render() {
|
|
const { account, groupIds } = this.props
|
|
const { currentIndex, submittedFirstPost } = this.state
|
|
|
|
const pages = [
|
|
<SlideWelcome />,
|
|
<SlidePhotos
|
|
account={account}
|
|
onSave={this.handleOnSaveUserProfileInformation}
|
|
/>,
|
|
<SlideGroups groupIds={groupIds} />,
|
|
<SlideFirstPost
|
|
submitted={submittedFirstPost}
|
|
onNext={this.handleNext}
|
|
/>,
|
|
]
|
|
|
|
const titles = [
|
|
`Welcome to Gab!`,
|
|
'Complete your profile',
|
|
'Find your people',
|
|
'Start a conversation',
|
|
]
|
|
const title = titles[currentIndex]
|
|
|
|
const nextTitle = currentIndex === 3 ? 'Finish' : 'Next'
|
|
|
|
return (
|
|
<div className={[_s.d, _s.w100PC, _s.maxH80VH].join(' ')}>
|
|
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.jcCenter, _s.borderBottom1PX, _s.borderColorSecondary, _s.h53PX, _s.px15].join(' ')}>
|
|
<Responsive min={BREAKPOINT_EXTRA_SMALL}>
|
|
<Heading>
|
|
{title}
|
|
</Heading>
|
|
</Responsive>
|
|
<Responsive max={BREAKPOINT_EXTRA_SMALL}>
|
|
<Heading size='h2'>
|
|
{title}
|
|
</Heading>
|
|
</Responsive>
|
|
<div className={[_s.mlAuto].join(' ')}>
|
|
<Button
|
|
href={currentIndex === 3 ? '/home' : undefined}
|
|
onClick={this.handleNext}
|
|
className={_s.px10}
|
|
icon={currentIndex !== 3 ? 'arrow-right' : undefined}
|
|
iconSize={currentIndex !== 3 ? '18px' : undefined}
|
|
>
|
|
{
|
|
currentIndex === 3 &&
|
|
<React.Fragment>
|
|
<Responsive min={BREAKPOINT_EXTRA_SMALL}>
|
|
<Text color='white' className={_s.px5}>{nextTitle}</Text>
|
|
</Responsive>
|
|
<Responsive max={BREAKPOINT_EXTRA_SMALL}>
|
|
<Icon id='check' size='14px' className={_s.cWhite} />
|
|
</Responsive>
|
|
</React.Fragment>
|
|
}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<ReactSwipeableViews
|
|
index={currentIndex}
|
|
onChangeIndex={this.handleSwipe}
|
|
className={[_s.d, _s.flexNormal, _s.calcH80VH106PX].join(' ')}
|
|
containerStyle={{
|
|
width: '100%',
|
|
}}
|
|
slideStyle={{
|
|
// height: '100%',
|
|
}}
|
|
>
|
|
{
|
|
pages.map((page, i) => (
|
|
<div key={i} className={[_s.d, _s.calcH80VH106PX].join(' ')}>
|
|
{page}
|
|
</div>
|
|
))
|
|
}
|
|
</ReactSwipeableViews>
|
|
|
|
<div className={[_s.d, _s.px15, _s.h53PX, _s.aiCenter, _s.jcCenter, _s.borderTop1PX, _s.borderColorSecondary, _s.w100PC, _s.flexRow].join(' ')}>
|
|
<div className={[_s.d, _s.w50PX, _s.mrAuto].join(' ')}>
|
|
{
|
|
currentIndex !== 0 &&
|
|
<Button
|
|
className={_s.opacity05}
|
|
onClick={this.handlePrev}
|
|
icon='arrow-left'
|
|
backgroundColor='none'
|
|
color='secondary'
|
|
iconSize='20px'
|
|
/>
|
|
}
|
|
</div>
|
|
|
|
<div className={[_s.d, _s.h100PC, _s.flexGrow1, _s.aiCenter, _s.jcCenter].join(' ')}>
|
|
<Pagination
|
|
count={pages.length}
|
|
activeIndex={currentIndex}
|
|
onClick={this.handleDot}
|
|
color='brand'
|
|
/>
|
|
</div>
|
|
|
|
<Button
|
|
isText
|
|
href={currentIndex === 3 ? '/home' : undefined}
|
|
className={[_s.d, _s.w50PX, _s.h100PC, _s.jcCenter, _s.pr0, _s.pl0, _s.mlAuto, _s.opacity05].join(' ')}
|
|
onClick={this.handleNext}
|
|
backgroundColor='none'
|
|
color='secondary'
|
|
>
|
|
<Text color='inherit' align='right'>{nextTitle}</Text>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
}
|
|
|
|
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) => ({
|
|
onSaveShownOnboarding: () => dispatch(saveShownOnboarding()),
|
|
onFetchFeaturedGroups: () => dispatch(fetchGroups('featured')),
|
|
onSaveUserProfileInformation(data) {
|
|
dispatch(saveUserProfileInformation(data))
|
|
},
|
|
})
|
|
|
|
Introduction.propTypes = {
|
|
account: ImmutablePropTypes.map.isRequired,
|
|
groupIds: ImmutablePropTypes.list,
|
|
isSubmitting: PropTypes.bool.isRequired,
|
|
shownOnboarding: PropTypes.bool.isRequired,
|
|
onSaveShownOnboarding: PropTypes.func.isRequired,
|
|
onFetchFeaturedGroups: PropTypes.func.isRequired,
|
|
onSaveUserProfileInformation: PropTypes.func.isRequired,
|
|
}
|
|
|
|
export default connect(mapStateToProps, mapDispatchToProps)(Introduction) |