2018-03-26 23:32:43 +02:00
// @flow
import * as React from 'react' ;
2018-03-30 07:37:09 +02:00
import { isNameValid , buildURI , regexInvalidURI } from 'lbryURI' ;
2018-03-26 23:32:43 +02:00
import { Form , FormField , FormRow , FormFieldPrice , Submit } from 'component/common/form' ;
import Button from 'component/button' ;
import ChannelSection from 'component/selectChannel' ;
import classnames from 'classnames' ;
import type { PublishParams , UpdatePublishFormData } from 'redux/reducers/publish' ;
import FileSelector from 'component/common/file-selector' ;
import { COPYRIGHT , OTHER } from 'constants/licenses' ;
2018-03-30 07:37:09 +02:00
import { CHANNEL _NEW , CHANNEL _ANONYMOUS , MINIMUM _PUBLISH _BID } from 'constants/claim' ;
2018-03-26 23:32:43 +02:00
import * as icons from 'constants/icons' ;
2018-03-30 07:37:09 +02:00
import BidHelpText from './internal/bid-help-text' ;
import LicenseType from './internal/license-type' ;
2018-03-26 23:32:43 +02:00
type Props = {
publish : PublishParams => void ,
filePath : ? string ,
bid : ? number ,
editingURI : ? string ,
title : ? string ,
thumbnail : ? string ,
description : ? string ,
language : string ,
nsfw : boolean ,
contentIsFree : boolean ,
price : {
amount : number ,
currency : string ,
} ,
channel : string ,
name : ? string ,
tosAccepted : boolean ,
updatePublishForm : UpdatePublishFormData => void ,
bid : number ,
nameError : ? string ,
isResolvingUri : boolean ,
winningBidForClaimUri : number ,
myClaimForUri : ? {
amount : number ,
2018-04-04 01:46:03 +02:00
value : {
stream : {
source : { source : string } ,
} ,
} ,
2018-03-26 23:32:43 +02:00
} ,
licenseType : string ,
otherLicenseDescription : ? string ,
licenseUrl : ? string ,
copyrightNotice : ? string ,
uri : ? string ,
bidError : ? string ,
publishing : boolean ,
balance : number ,
clearPublish : ( ) => void ,
resolveUri : string => void ,
scrollToTop : ( ) => void ,
2018-04-06 08:22:35 +02:00
prepareEdit : ( { } , uri ) => void ,
2018-03-26 23:32:43 +02:00
} ;
class PublishForm extends React . PureComponent < Props > {
constructor ( props : Props ) {
2017-06-30 10:45:54 +02:00
super ( props ) ;
2018-03-26 23:32:43 +02:00
( this : any ) . handleFileChange = this . handleFileChange . bind ( this ) ;
( this : any ) . checkIsFormValid = this . checkIsFormValid . bind ( this ) ;
( this : any ) . renderFormErrors = this . renderFormErrors . bind ( this ) ;
( this : any ) . handlePublish = this . handlePublish . bind ( this ) ;
( this : any ) . handleCancelPublish = this . handleCancelPublish . bind ( this ) ;
( this : any ) . handleNameChange = this . handleNameChange . bind ( this ) ;
( this : any ) . handleChannelChange = this . handleChannelChange . bind ( this ) ;
( this : any ) . editExistingClaim = this . editExistingClaim . bind ( this ) ;
( this : any ) . getNewUri = this . getNewUri . bind ( this ) ;
}
2018-04-04 01:17:40 +02:00
// Returns a new uri to be used in the form and begins to resolve that uri for bid help text
getNewUri ( name : string , channel : string ) {
const { resolveUri } = this . props ;
// If they are midway through a channel creation, treat it as anonymous until it completes
const channelName = channel === CHANNEL _ANONYMOUS || channel === CHANNEL _NEW ? '' : channel ;
2018-03-26 23:32:43 +02:00
2018-04-04 01:17:40 +02:00
let uri ;
try {
uri = buildURI ( { contentName : name , channelName } ) ;
} catch ( e ) {
// something wrong with channel or name
2017-06-30 10:45:54 +02:00
}
2018-04-04 01:17:40 +02:00
if ( uri ) {
resolveUri ( uri ) ;
return uri ;
2017-08-26 04:09:56 +02:00
}
2018-04-04 01:17:40 +02:00
return '' ;
2017-08-26 04:09:56 +02:00
}
2018-03-26 23:32:43 +02:00
handleFileChange ( filePath : string , fileName : string ) {
2018-04-03 06:05:11 +02:00
const { updatePublishForm , channel , name } = this . props ;
const newFileParams : {
filePath : string ,
name ? : string ,
2018-04-04 01:17:40 +02:00
uri ? : string ,
2018-04-03 06:05:11 +02:00
} = { filePath } ;
2017-06-30 10:45:54 +02:00
2018-04-03 06:05:11 +02:00
if ( ! name ) {
const parsedFileName = fileName . replace ( regexInvalidURI , '' ) ;
const uri = this . getNewUri ( parsedFileName , channel ) ;
newFileParams . name = parsedFileName ;
2018-04-04 01:17:40 +02:00
newFileParams . uri = uri ;
2018-03-26 23:32:43 +02:00
}
2018-04-03 06:05:11 +02:00
updatePublishForm ( newFileParams ) ;
2017-06-30 10:45:54 +02:00
}
2018-03-26 23:32:43 +02:00
handleNameChange ( name : ? string ) {
const { channel , updatePublishForm } = this . props ;
2017-06-30 10:45:54 +02:00
2018-03-26 23:32:43 +02:00
if ( ! name ) {
updatePublishForm ( { name , nameError : undefined } ) ;
2017-06-30 10:45:54 +02:00
return ;
}
2018-03-26 23:32:43 +02:00
if ( ! isNameValid ( name , false ) ) {
updatePublishForm ( {
name ,
nameError : _ _ ( 'LBRY names must contain only letters, numbers and dashes.' ) ,
} ) ;
2017-06-30 10:45:54 +02:00
return ;
}
2018-03-26 23:32:43 +02:00
const uri = this . getNewUri ( name , channel ) ;
updatePublishForm ( {
2017-12-21 22:08:54 +01:00
name ,
2017-06-30 10:45:54 +02:00
uri ,
2018-03-26 23:32:43 +02:00
nameError : undefined ,
2017-06-30 10:45:54 +02:00
} ) ;
}
2018-03-26 23:32:43 +02:00
handleChannelChange ( channelName : string ) {
const { name , updatePublishForm } = this . props ;
if ( name ) {
const uri = this . getNewUri ( name , channelName ) ;
updatePublishForm ( { channel : channelName , uri } ) ;
2017-06-30 10:45:54 +02:00
} else {
2018-03-26 23:32:43 +02:00
updatePublishForm ( { channel : channelName } ) ;
2017-06-30 10:45:54 +02:00
}
}
2018-03-26 23:32:43 +02:00
handleBidChange ( bid : number ) {
const { balance , updatePublishForm } = this . props ;
2017-06-30 10:45:54 +02:00
2018-03-26 23:32:43 +02:00
let bidError ;
if ( balance <= bid ) {
bidError = _ _ ( 'Not enough credits' ) ;
} else if ( bid <= MINIMUM _PUBLISH _BID ) {
bidError = _ _ ( 'Your bid must be higher' ) ;
}
2017-06-30 10:45:54 +02:00
2018-03-26 23:32:43 +02:00
updatePublishForm ( { bid , bidError } ) ;
2017-06-30 10:45:54 +02:00
}
2018-04-06 08:22:35 +02:00
editExistingClaim ( myClaimForUri : ? { } , uri : string ) {
const { prepareEdit , scrollToTop } = this . props ;
2018-04-04 01:17:40 +02:00
if ( myClaimForUri ) {
2018-04-06 08:22:35 +02:00
prepareEdit ( myClaimForUri , uri ) ;
2018-04-04 01:17:40 +02:00
scrollToTop ( ) ;
2017-06-30 10:45:54 +02:00
}
2018-04-04 01:17:40 +02:00
}
2017-06-30 10:45:54 +02:00
2018-04-04 01:17:40 +02:00
handleCancelPublish ( ) {
const { clearPublish , scrollToTop } = this . props ;
scrollToTop ( ) ;
clearPublish ( ) ;
}
handlePublish ( ) {
const {
publish ,
filePath ,
bid ,
title ,
thumbnail ,
description ,
language ,
nsfw ,
channel ,
licenseType ,
licenseUrl ,
otherLicenseDescription ,
copyrightNotice ,
name ,
contentIsFree ,
price ,
uri ,
2018-04-04 01:46:03 +02:00
myClaimForUri ,
2018-04-04 01:17:40 +02:00
} = this . props ;
let publishingLicense ;
switch ( licenseType ) {
case COPYRIGHT :
publishingLicense = copyrightNotice ;
break ;
case OTHER :
publishingLicense = otherLicenseDescription ;
break ;
default :
publishingLicense = licenseType ;
2017-06-30 10:45:54 +02:00
}
2018-04-04 01:17:40 +02:00
const publishingLicenseUrl = licenseType === COPYRIGHT ? '' : licenseUrl ;
const publishParams = {
filePath ,
bid ,
title ,
thumbnail ,
description ,
language ,
nsfw ,
channel ,
license : publishingLicense ,
licenseUrl : publishingLicenseUrl ,
otherLicenseDescription ,
copyrightNotice ,
name ,
contentIsFree ,
price ,
uri ,
} ;
2018-04-04 01:46:03 +02:00
// Editing a claim
if ( ! filePath && myClaimForUri ) {
const { source } = myClaimForUri . value . stream ;
2018-04-04 19:18:52 +02:00
publishParams . sources = source ;
2018-04-04 01:46:03 +02:00
}
2018-04-04 01:17:40 +02:00
publish ( publishParams ) ;
2017-06-30 10:45:54 +02:00
}
2018-03-26 23:32:43 +02:00
checkIsFormValid ( ) {
const { name , nameError , title , bid , bidError , tosAccepted } = this . props ;
return name && ! nameError && title && bid && ! bidError && tosAccepted ;
2017-07-22 11:10:37 +02:00
}
2018-03-26 23:32:43 +02:00
renderFormErrors ( ) {
const { name , nameError , title , bid , bidError , tosAccepted } = this . props ;
2017-06-30 10:45:54 +02:00
2018-03-26 23:32:43 +02:00
if ( nameError || bidError ) {
// There will be inline errors if either of these exist
// These are just extra help at the bottom of the screen
// There could be multiple bid errors, so just duplicate it at the bottom
2017-06-30 10:45:54 +02:00
return (
2018-03-26 23:32:43 +02:00
< div className = "card__subtitle form-field__error" >
{ nameError && < div > { _ _ ( 'The URL you created is not valid.' ) } < / div > }
{ bidError && < div > { bidError } < / div > }
< / div >
2017-12-21 22:08:54 +01:00
) ;
2017-06-30 10:45:54 +02:00
}
2018-03-26 23:32:43 +02:00
return (
< div className = "card__content card__subtitle card__subtitle--block form-field__error" >
{ ! title && < div > { _ _ ( 'A title is required' ) } < / div > }
{ ! name && < div > { _ _ ( 'A URL is required' ) } < / div > }
{ ! bid && < div > { _ _ ( 'A bid amount is required' ) } < / div > }
{ ! tosAccepted && < div > { _ _ ( 'You must agree to the terms of service' ) } < / div > }
< / div >
) ;
2017-06-30 10:45:54 +02:00
}
render ( ) {
2018-03-26 23:32:43 +02:00
const {
filePath ,
editingURI ,
title ,
thumbnail ,
description ,
language ,
nsfw ,
contentIsFree ,
price ,
channel ,
name ,
tosAccepted ,
updatePublishForm ,
bid ,
nameError ,
isResolvingUri ,
winningBidForClaimUri ,
myClaimForUri ,
licenseType ,
otherLicenseDescription ,
licenseUrl ,
copyrightNotice ,
uri ,
bidError ,
publishing ,
clearPublish ,
} = this . props ;
const formDisabled = ( ! filePath && ! editingURI ) || publishing ;
const formValid = this . checkIsFormValid ( ) ;
2018-04-06 08:22:35 +02:00
const simpleUri = uri && uri . split ( '#' ) [ 0 ] ;
const isStillEditing = editingURI === simpleUri ;
2018-03-26 23:32:43 +02:00
let submitLabel ;
if ( isStillEditing ) {
submitLabel = ! publishing ? _ _ ( 'Edit' ) : _ _ ( 'Editing...' ) ;
} else {
submitLabel = ! publishing ? _ _ ( 'Publish' ) : _ _ ( 'Publishing...' ) ;
2017-09-05 03:03:48 +02:00
}
2017-06-30 10:45:54 +02:00
return (
2018-03-26 23:32:43 +02:00
< Form onSubmit = { this . handlePublish } >
< section className = { classnames ( 'card card--section' ) } >
< div className = "card__title" > { _ _ ( 'Content' ) } < / div >
< div className = "card__subtitle" >
{ editingURI ? _ _ ( 'Editing a claim' ) : _ _ ( 'What are you publishing?' ) }
< / div >
{ ( filePath || ! ! editingURI ) && (
< div className = "card-media__internal-links" >
< Button
button = "inverse"
icon = { icons . CLOSE }
label = { _ _ ( 'Clear' ) }
onClick = { clearPublish }
2017-06-30 10:45:54 +02:00
/ >
< / div >
2018-03-26 23:32:43 +02:00
) }
< FileSelector currentPath = { filePath } onFileChosen = { this . handleFileChange } / >
{ ! ! editingURI && (
< p className = "card__content card__subtitle" >
{ _ _ ( "If you don't choose a file, the file from your existing claim" ) }
{ ` " ${ name } " ` }
{ _ _ ( 'will be used.' ) }
< / p >
) }
< / section >
< div className = { classnames ( { 'card--disabled' : formDisabled } ) } >
< section className = "card card--section" >
< FormRow >
< FormField
stretch
type = "text"
name = "content_title"
label = { _ _ ( 'Title' ) }
placeholder = { _ _ ( 'Titular Title' ) }
disabled = { formDisabled }
value = { title }
onChange = { e => updatePublishForm ( { title : e . target . value } ) }
/ >
< / FormRow >
< FormRow padded >
< FormField
stretch
type = "text"
name = "content_thumbnail"
label = { _ _ ( 'Thumbnail' ) }
placeholder = "http://spee.ch/mylogo"
value = { thumbnail }
disabled = { formDisabled }
onChange = { e => updatePublishForm ( { thumbnail : e . target . value } ) }
/ >
< / FormRow >
< FormRow padded >
< FormField
stretch
type = "markdown"
name = "content_description"
label = { _ _ ( 'Description' ) }
placeholder = { _ _ ( 'Description of your content' ) }
value = { description }
disabled = { formDisabled }
onChange = { text => updatePublishForm ( { description : text } ) }
/ >
< / FormRow >
2017-06-30 10:45:54 +02:00
< / section >
2018-03-26 23:32:43 +02:00
< section className = "card card--section" >
< div className = "card__title" > { _ _ ( 'Price' ) } < / div >
< div className = "card__subtitle" > { _ _ ( 'How much will this content cost?' ) } < / div >
2017-06-30 10:45:54 +02:00
< div className = "card__content" >
2018-03-26 23:32:43 +02:00
< FormField
2017-06-30 10:45:54 +02:00
type = "radio"
2018-03-26 23:32:43 +02:00
name = "content_free"
postfix = { _ _ ( 'Free' ) }
checked = { contentIsFree }
disabled = { formDisabled }
onChange = { ( ) => updatePublishForm ( { contentIsFree : true } ) }
2017-06-30 10:45:54 +02:00
/ >
< FormField
type = "radio"
2018-03-26 23:32:43 +02:00
name = "content_cost"
postfix = { _ _ ( 'Choose price' ) }
checked = { ! contentIsFree }
disabled = { formDisabled }
onChange = { ( ) => updatePublishForm ( { contentIsFree : false } ) }
2017-06-30 10:45:54 +02:00
/ >
2018-04-12 01:41:53 +02:00
{ ! contentIsFree && (
< FormFieldPrice
name = "content_cost_amount"
min = "0"
price = { price }
onChange = { newPrice => updatePublishForm ( { price : newPrice } ) }
/ >
) }
2018-03-26 23:32:43 +02:00
{ price . currency !== 'LBC' && (
< p className = "form-field__help" >
2017-11-21 20:51:12 +01:00
{ _ _ (
2017-12-21 22:08:54 +01:00
'All content fees are charged in LBC. For non-LBC payment methods, the number of credits charged will be adjusted based on the value of LBRY credits at the time of purchase.'
2017-11-21 20:51:12 +01:00
) }
2018-03-26 23:32:43 +02:00
< / p >
) }
2017-10-14 21:41:04 +02:00
< / div >
< / section >
2017-06-30 10:45:54 +02:00
2018-03-26 23:32:43 +02:00
< section className = "card card--section" >
< div className = "card__title" > { _ _ ( 'Anonymous or under a channel?' ) } < / div >
< p className = "card__subtitle" >
{ _ _ ( 'This is a username or handle that your content can be found under.' ) } { ' ' }
{ _ _ ( 'Ex. @Marvel, @TheBeatles, @BooksByJoe' ) }
< / p >
< ChannelSection channel = { channel } onChannelChange = { this . handleChannelChange } / >
< / section >
2017-11-21 20:51:12 +01:00
2018-03-26 23:32:43 +02:00
< section className = "card card--section" >
< div className = "card__title" > { _ _ ( 'Where can people find this content?' ) } < / div >
< p className = "card__subtitle" >
{ _ _ (
'The LBRY URL is the exact address where people find your content (ex. lbry://myvideo).'
) } { ' ' }
< Button button = "link" label = { _ _ ( 'Learn more' ) } href = "https://lbry.io/faq/naming" / >
< / p >
< div className = "card__content" >
< FormRow >
< FormField
stretch
prefix = { ` lbry:// ${
2018-04-04 01:17:40 +02:00
! channel || channel === CHANNEL _ANONYMOUS || channel === CHANNEL _NEW
? ''
: ` ${ channel } / `
2018-03-26 23:32:43 +02:00
} ` }
2017-11-21 20:51:12 +01:00
type = "text"
2018-03-26 23:32:43 +02:00
name = "content_name"
placeholder = "myname"
value = { name }
onChange = { event => this . handleNameChange ( event . target . value ) }
error = { nameError }
helper = {
< BidHelpText
2018-04-06 08:22:35 +02:00
uri = { simpleUri }
2018-03-26 23:32:43 +02:00
editingURI = { editingURI }
isResolvingUri = { isResolvingUri }
winningBidForClaimUri = { winningBidForClaimUri }
2018-04-06 08:22:35 +02:00
myClaimForUri = { myClaimForUri }
2018-03-26 23:32:43 +02:00
onEditMyClaim = { this . editExistingClaim }
/ >
}
2017-11-21 20:51:12 +01:00
/ >
2018-03-26 23:32:43 +02:00
< / FormRow >
< / div >
< div className = { classnames ( 'card__content' , { 'card--disabled' : ! name } ) } >
< FormField
className = "input--price-amount"
type = "number"
name = "content_bid"
step = "any"
label = { _ _ ( 'Deposit' ) }
postfix = "LBC"
value = { bid }
error = { bidError }
min = "0"
disabled = { ! name }
onChange = { event => this . handleBidChange ( parseFloat ( event . target . value ) ) }
helper = { _ _ ( 'This LBC remains yours and the deposit can be undone at any time.' ) }
placeholder = { winningBidForClaimUri ? winningBidForClaimUri + 0.1 : 0.1 }
/ >
2017-06-30 10:45:54 +02:00
< / div >
< / section >
2018-03-26 23:32:43 +02:00
< section className = "card card--section" >
< FormRow >
< FormField
type = "checkbox"
name = "content_is_mature"
postfix = { _ _ ( 'Mature audiences only' ) }
checked = { nsfw }
onChange = { event => updatePublishForm ( { nsfw : event . target . checked } ) }
2017-06-30 10:45:54 +02:00
/ >
2018-03-26 23:32:43 +02:00
< / FormRow >
< FormRow padded >
< FormField
label = { _ _ ( 'Language' ) }
type = "select"
name = "content_language"
value = { language }
onChange = { event => updatePublishForm ( { language : event . target . value } ) }
>
< option value = "en" > { _ _ ( 'English' ) } < / option >
< option value = "zh" > { _ _ ( 'Chinese' ) } < / option >
< option value = "fr" > { _ _ ( 'French' ) } < / option >
< option value = "de" > { _ _ ( 'German' ) } < / option >
< option value = "jp" > { _ _ ( 'Japanese' ) } < / option >
< option value = "ru" > { _ _ ( 'Russian' ) } < / option >
< option value = "es" > { _ _ ( 'Spanish' ) } < / option >
< / FormField >
< / FormRow >
< LicenseType
licenseType = { licenseType }
otherLicenseDescription = { otherLicenseDescription }
licenseUrl = { licenseUrl }
copyrightNotice = { copyrightNotice }
handleLicenseChange = { ( newLicenseType , newLicenseUrl ) =>
updatePublishForm ( {
licenseType : newLicenseType ,
licenseUrl : newLicenseUrl ,
} )
}
handleLicenseDescriptionChange = { event =>
updatePublishForm ( {
otherLicenseDescription : event . target . value ,
} )
}
handleLicenseUrlChange = { event =>
updatePublishForm ( { licenseUrl : event . target . value } )
}
handleCopyrightNoticeChange = { event =>
updatePublishForm ( { copyrightNotice : event . target . value } )
}
/ >
2017-06-30 10:45:54 +02:00
< / section >
2018-03-26 23:32:43 +02:00
< section className = "card card--section" >
< div className = "card__title" > { _ _ ( 'Terms of Service' ) } < / div >
2017-06-30 10:45:54 +02:00
< div className = "card__content" >
2018-03-26 23:32:43 +02:00
< FormField
name = "lbry_tos"
type = "checkbox"
checked = { tosAccepted }
postfix = {
2017-06-30 10:45:54 +02:00
< span >
2017-12-21 22:08:54 +01:00
{ _ _ ( 'I agree to the' ) } { ' ' }
2018-03-26 23:32:43 +02:00
< Button
button = "link"
2017-06-30 10:45:54 +02:00
href = "https://www.lbry.io/termsofservice"
2017-12-21 22:08:54 +01:00
label = { _ _ ( 'LBRY terms of service' ) }
2017-06-30 10:45:54 +02:00
/ >
< / span >
}
2018-03-26 23:32:43 +02:00
onChange = { event => updatePublishForm ( { tosAccepted : event . target . checked } ) }
2017-06-30 10:45:54 +02:00
/ >
< / div >
< / section >
2018-03-26 23:32:43 +02:00
< div className = "card__actions" >
< Submit label = { submitLabel } disabled = { formDisabled || ! formValid || publishing } / >
< Button button = "alt" onClick = { this . handleCancelPublish } label = { _ _ ( 'Cancel' ) } / >
2017-06-30 10:45:54 +02:00
< / div >
2018-03-26 23:32:43 +02:00
{ ! formDisabled && ! formValid && this . renderFormErrors ( ) }
< / div >
< / Form >
2017-06-30 10:45:54 +02:00
) ;
}
}
export default PublishForm ;