2020-04-21 16:00:05 -07:00
import React from 'react' ;
import { connect } from 'react-redux' ;
2020-06-06 13:52:33 -07:00
import { defineMessages , injectIntl , FormattedMessage } from 'react-intl' ;
2020-04-21 16:00:05 -07:00
import ImmutablePureComponent from 'react-immutable-pure-component' ;
import PropTypes from 'prop-types' ;
import ImmutablePropTypes from 'react-immutable-proptypes' ;
2020-08-09 13:13:36 -07:00
import { showAlert } from 'soapbox/actions/alerts' ;
2020-04-21 16:00:05 -07:00
import Column from '../ui/components/column' ;
import {
SimpleForm ,
FieldsGroup ,
TextInput ,
2020-04-21 17:22:00 -07:00
Checkbox ,
2020-04-22 14:26:44 -07:00
FileChooser ,
2020-08-05 17:08:38 -07:00
SimpleTextarea ,
2020-05-28 15:52:07 -07:00
} from 'soapbox/features/forms' ;
2020-04-22 14:26:44 -07:00
import ProfilePreview from './components/profile_preview' ;
2020-05-18 10:20:39 -07:00
import {
Map as ImmutableMap ,
List as ImmutableList ,
} from 'immutable' ;
2020-05-28 15:52:07 -07:00
import { patchMe } from 'soapbox/actions/me' ;
2020-08-09 13:13:36 -07:00
import { unescape } from 'lodash' ;
2020-04-21 16:00:05 -07:00
const messages = defineMessages ( {
heading : { id : 'column.edit_profile' , defaultMessage : 'Edit profile' } ,
2020-06-06 13:52:33 -07:00
metaFieldLabel : { id : 'edit_profile.fields.meta_fields.label_placeholder' , defaultMessage : 'Label' } ,
metaFieldContent : { id : 'edit_profile.fields.meta_fields.content_placeholder' , defaultMessage : 'Content' } ,
2020-08-05 10:47:45 -07:00
verified : { id : 'edit_profile.fields.verified_display_name' , defaultMessage : 'Verified users may not update their display name' } ,
2020-04-21 16:00:05 -07:00
} ) ;
const mapStateToProps = state => {
const me = state . get ( 'me' ) ;
return {
account : state . getIn ( [ 'accounts' , me ] ) ,
2020-08-09 11:58:15 -07:00
maxFields : state . getIn ( [ 'instance' , 'pleroma' , 'metadata' , 'fields_limits' , 'max_fields' ] , 4 ) ,
2020-04-21 16:00:05 -07:00
} ;
} ;
2020-08-08 18:32:39 -07:00
// Forces fields to be maxFields size, filling empty values
const normalizeFields = ( fields , maxFields ) => (
ImmutableList ( fields ) . setSize ( maxFields ) . map ( field =>
2020-05-28 13:29:32 -07:00
field ? field : ImmutableMap ( { name : '' , value : '' } )
2020-05-18 10:20:39 -07:00
)
) ;
2020-05-28 13:04:56 -07:00
// HTML unescape for special chars, eg <br>
const unescapeParams = ( map , params ) => (
params . reduce ( ( map , param ) => (
map . set ( param , unescape ( map . get ( param ) ) )
) , map )
) ;
2020-04-21 16:00:05 -07:00
export default @ connect ( mapStateToProps )
@ injectIntl
class EditProfile extends ImmutablePureComponent {
static propTypes = {
dispatch : PropTypes . func . isRequired ,
intl : PropTypes . object . isRequired ,
account : ImmutablePropTypes . map ,
2020-08-08 18:32:39 -07:00
maxFields : PropTypes . number ,
2020-04-21 16:00:05 -07:00
} ;
2020-04-21 17:22:00 -07:00
state = {
isLoading : false ,
2020-04-21 16:00:05 -07:00
}
2020-07-04 17:32:07 -07:00
constructor ( props ) {
super ( props ) ;
const initialState = props . account . withMutations ( map => {
map . merge ( map . get ( 'source' ) ) ;
map . delete ( 'source' ) ;
2020-08-08 18:32:39 -07:00
map . set ( 'fields' , normalizeFields ( map . get ( 'fields' ) , props . maxFields ) ) ;
2020-08-09 13:13:36 -07:00
unescapeParams ( map , [ 'display_name' , 'bio' ] ) ;
2020-07-04 17:32:07 -07:00
} ) ;
this . state = initialState . toObject ( ) ;
}
2020-04-22 15:04:08 -07:00
makePreviewAccount = ( ) => {
const { account } = this . props ;
return account . merge ( ImmutableMap ( {
header : this . state . header ,
avatar : this . state . avatar ,
display _name : this . state . display _name ,
} ) ) ;
}
2020-05-18 10:20:39 -07:00
getFieldParams = ( ) => {
let params = ImmutableMap ( ) ;
this . state . fields . forEach ( ( f , i ) =>
params = params
. set ( ` fields_attributes[ ${ i } ][name] ` , f . get ( 'name' ) )
. set ( ` fields_attributes[ ${ i } ][value] ` , f . get ( 'value' ) )
) ;
return params ;
}
2020-04-21 17:22:00 -07:00
getParams = ( ) => {
const { state } = this ;
2020-05-18 10:20:39 -07:00
return Object . assign ( {
2020-04-21 17:22:00 -07:00
discoverable : state . discoverable ,
bot : state . bot ,
display _name : state . display _name ,
note : state . note ,
2020-04-22 15:04:08 -07:00
avatar : state . avatar _file ,
header : state . header _file ,
2020-04-21 17:22:00 -07:00
locked : state . locked ,
2020-05-18 10:20:39 -07:00
} , this . getFieldParams ( ) . toJS ( ) ) ;
2020-04-21 16:00:05 -07:00
}
2020-04-22 15:04:08 -07:00
getFormdata = ( ) => {
const data = this . getParams ( ) ;
let formData = new FormData ( ) ;
for ( let key in data ) {
2020-08-09 13:13:36 -07:00
// Compact the submission. This should probably be done better.
2020-08-10 14:17:39 -07:00
const shouldAppend = Boolean ( data [ key ] !== undefined || key . startsWith ( 'fields_attributes' ) ) ;
2020-05-18 10:20:39 -07:00
if ( shouldAppend ) formData . append ( key , data [ key ] || '' ) ;
2020-04-22 15:04:08 -07:00
}
return formData ;
}
2020-04-21 16:00:05 -07:00
handleSubmit = ( event ) => {
const { dispatch } = this . props ;
2020-04-22 15:04:08 -07:00
dispatch ( patchMe ( this . getFormdata ( ) ) ) . then ( ( ) => {
2020-04-21 16:00:05 -07:00
this . setState ( { isLoading : false } ) ;
2020-08-09 13:13:36 -07:00
dispatch ( showAlert ( '' , 'Profile saved!' ) ) ;
2020-04-21 16:00:05 -07:00
} ) . catch ( ( error ) => {
this . setState ( { isLoading : false } ) ;
} ) ;
this . setState ( { isLoading : true } ) ;
event . preventDefault ( ) ;
}
2020-04-21 17:22:00 -07:00
handleCheckboxChange = e => {
this . setState ( { [ e . target . name ] : e . target . checked } ) ;
}
handleTextChange = e => {
this . setState ( { [ e . target . name ] : e . target . value } ) ;
}
2020-05-18 10:20:39 -07:00
handleFieldChange = ( i , key ) => {
return ( e ) => {
this . setState ( {
fields : this . state . fields . setIn ( [ i , key ] , e . target . value ) ,
} ) ;
} ;
}
2020-04-22 15:04:08 -07:00
handleFileChange = e => {
const { name } = e . target ;
const [ file ] = e . target . files || [ ] ;
const url = file ? URL . createObjectURL ( file ) : this . state [ name ] ;
this . setState ( {
[ name ] : url ,
[ ` ${ name } _file ` ] : file ,
} ) ;
}
2020-04-21 16:00:05 -07:00
render ( ) {
2020-08-08 18:32:39 -07:00
const { intl , maxFields , account } = this . props ;
2020-08-09 12:55:32 -07:00
const verified = account . getIn ( [ 'pleroma' , 'tags' ] , ImmutableList ( ) ) . includes ( 'verified' ) ;
2020-04-21 16:00:05 -07:00
return (
2020-04-21 17:24:57 -07:00
< Column icon = 'user' heading = { intl . formatMessage ( messages . heading ) } backBtnSlim >
2020-04-21 16:00:05 -07:00
< SimpleForm onSubmit = { this . handleSubmit } >
< fieldset disabled = { this . state . isLoading } >
< FieldsGroup >
< TextInput
2020-08-09 12:32:22 -07:00
className = { verified ? 'disabled' : '' }
2020-06-06 13:52:33 -07:00
label = { < FormattedMessage id = 'edit_profile.fields.display_name_label' defaultMessage = 'Display name' / > }
2020-04-21 16:00:05 -07:00
name = 'display_name'
2020-04-21 17:22:00 -07:00
value = { this . state . display _name }
onChange = { this . handleTextChange }
2020-08-05 10:47:45 -07:00
disabled = { verified }
hint = { verified && intl . formatMessage ( messages . verified ) }
2020-04-21 16:00:05 -07:00
/ >
2020-08-05 17:08:38 -07:00
< SimpleTextarea
2020-06-06 13:52:33 -07:00
label = { < FormattedMessage id = 'edit_profile.fields.bio_label' defaultMessage = 'Bio' / > }
2020-04-21 16:00:05 -07:00
name = 'note'
2020-08-05 17:08:38 -07:00
autoComplete = 'off'
2020-08-09 13:13:36 -07:00
value = { this . state . note }
2020-08-05 17:08:38 -07:00
wrap = 'hard'
2020-08-09 13:13:36 -07:00
onChange = { this . handleTextChange }
rows = { 3 }
2020-04-21 17:22:00 -07:00
/ >
2020-04-22 14:26:44 -07:00
< div className = 'fields-row' >
< div className = 'fields-row__column fields-row__column-6' >
2020-04-22 15:04:08 -07:00
< ProfilePreview account = { this . makePreviewAccount ( ) } / >
2020-04-22 14:26:44 -07:00
< / d i v >
< div className = 'fields-row__column fields-group fields-row__column-6' >
< FileChooser
2020-06-06 13:52:33 -07:00
label = { < FormattedMessage id = 'edit_profile.fields.header_label' defaultMessage = 'Header' / > }
2020-04-22 14:26:44 -07:00
name = 'header'
2020-06-06 13:52:33 -07:00
hint = { < FormattedMessage id = 'edit_profile.hints.header' defaultMessage = 'PNG, GIF or JPG. At most 2 MB. Will be downscaled to 1500x500px' / > }
2020-04-22 15:04:08 -07:00
onChange = { this . handleFileChange }
2020-04-22 14:26:44 -07:00
/ >
< FileChooser
2020-06-06 13:52:33 -07:00
label = { < FormattedMessage id = 'edit_profile.fields.avatar_label' defaultMessage = 'Avatar' / > }
2020-04-22 14:26:44 -07:00
name = 'avatar'
2020-06-06 13:52:33 -07:00
hint = { < FormattedMessage id = 'edit_profile.hints.avatar' defaultMessage = 'PNG, GIF or JPG. At most 2 MB. Will be downscaled to 400x400px' / > }
2020-04-22 15:04:08 -07:00
onChange = { this . handleFileChange }
2020-04-22 14:26:44 -07:00
/ >
< / d i v >
< / d i v >
2020-04-21 17:22:00 -07:00
< Checkbox
2020-06-06 13:52:33 -07:00
label = { < FormattedMessage id = 'edit_profile.fields.locked_label' defaultMessage = 'Lock account' / > }
hint = { < FormattedMessage id = 'edit_profile.hints.locked' defaultMessage = 'Requires you to manually approve followers' / > }
2020-04-21 17:22:00 -07:00
name = 'locked'
checked = { this . state . locked }
onChange = { this . handleCheckboxChange }
/ >
< Checkbox
2020-06-06 13:52:33 -07:00
label = { < FormattedMessage id = 'edit_profile.fields.bot_label' defaultMessage = 'This is a bot account' / > }
hint = { < FormattedMessage id = 'edit_profile.hints.bot' defaultMessage = 'This account mainly performs automated actions and might not be monitored' / > }
2020-04-21 17:22:00 -07:00
name = 'bot'
checked = { this . state . bot }
onChange = { this . handleCheckboxChange }
2020-04-21 16:00:05 -07:00
/ >
< / F i e l d s G r o u p >
2020-05-18 10:20:39 -07:00
< FieldsGroup >
< div className = 'fields-row__column fields-group' >
< div className = 'input with_block_label' >
2020-06-06 13:52:33 -07:00
< label > < FormattedMessage id = 'edit_profile.fields.meta_fields_label' defaultMessage = 'Profile metadata' / > < / l a b e l >
< span className = 'hint' >
2020-08-08 18:32:39 -07:00
< FormattedMessage id = 'edit_profile.hints.meta_fields' defaultMessage = 'You can have up to {count, plural, one {# item} other {# items}} displayed as a table on your profile' values = { { count : maxFields } } / >
2020-06-06 13:52:33 -07:00
< / s p a n >
2020-05-18 10:20:39 -07:00
{
this . state . fields . map ( ( field , i ) => (
< div className = 'row' key = { i } >
< TextInput
2020-06-06 13:52:33 -07:00
placeholder = { intl . formatMessage ( messages . metaFieldLabel ) }
2020-05-18 10:20:39 -07:00
value = { field . get ( 'name' ) }
onChange = { this . handleFieldChange ( i , 'name' ) }
/ >
< TextInput
2020-06-06 13:52:33 -07:00
placeholder = { intl . formatMessage ( messages . metaFieldContent ) }
2020-05-18 10:20:39 -07:00
value = { field . get ( 'value' ) }
onChange = { this . handleFieldChange ( i , 'value' ) }
/ >
< / d i v >
) )
}
< / d i v >
< / d i v >
< / F i e l d s G r o u p >
2020-04-21 16:00:05 -07:00
< / f i e l d s e t >
< div className = 'actions' >
2020-06-06 13:52:33 -07:00
< button name = 'button' type = 'submit' className = 'btn button button-primary' >
< FormattedMessage id = 'edit_profile.save' defaultMessage = 'Save' / >
< / b u t t o n >
2020-04-21 16:00:05 -07:00
< / d i v >
< / S i m p l e F o r m >
< / C o l u m n >
) ;
}
}