// @flow import * as MODALS from 'constants/modal_types'; import * as ICONS from 'constants/icons'; import React from 'react'; import classnames from 'classnames'; import { FormField } from 'component/common/form'; import Button from 'component/button'; import TagsSearch from 'component/tagsSearch'; import ErrorText from 'component/common/error-text'; import ChannelThumbnail from 'component/channelThumbnail'; import { isNameValid, parseURI } from 'util/lbryURI'; import ClaimAbandonButton from 'component/claimAbandonButton'; import { useHistory } from 'react-router-dom'; import { MINIMUM_PUBLISH_BID, INVALID_NAME_ERROR, ESTIMATED_FEE } from 'constants/claim'; import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs'; import Card from 'component/common/card'; import * as PAGES from 'constants/pages'; import analytics from 'analytics'; import LbcSymbol from 'component/common/lbc-symbol'; import SUPPORTED_LANGUAGES from 'constants/supported_languages'; import WalletSpendableBalanceHelp from 'component/walletSpendableBalanceHelp'; import { sortLanguageMap } from 'util/default-languages'; import ThumbnailBrokenImage from 'component/selectThumbnail/thumbnail-broken.png'; import Gerbil from 'component/channelThumbnail/gerbil.png'; const LANG_NONE = 'none'; const MAX_TAG_SELECT = 5; const MAX_NAME_LEN = 128; const MAX_TITLE_LEN = 255; const MAX_DESCRIPTION_LEN = 2056; type Props = { claim: ChannelClaim, title: string, amount: number, coverUrl: string, thumbnailUrl: string, location: { search: string }, description: string, website: string, email: string, balance: number, tags: Array, locations: Array, languages: Array, updateChannel: (any) => Promise, updatingChannel: boolean, updateError: string, createChannel: (any) => Promise, createError: string, creatingChannel: boolean, clearChannelErrors: () => void, claimInitialRewards: () => void, onDone: () => void, openModal: ( id: string, { onUpdate: (string, boolean) => void, assetName: string, helpText: string, currentValue: string, title: string } ) => void, uri: string, disabled: boolean, isClaimingInitialRewards: boolean, hasClaimedInitialRewards: boolean, }; function ChannelForm(props: Props) { const { uri, claim, amount, title, description, website, email, thumbnailUrl, coverUrl, tags, locations, languages = [], onDone, updateChannel, updateError, updatingChannel, createChannel, creatingChannel, createError, clearChannelErrors, claimInitialRewards, openModal, disabled, isClaimingInitialRewards, hasClaimedInitialRewards, } = props; const [nameError, setNameError] = React.useState(undefined); const [bidError, setBidError] = React.useState(''); const [isUpload, setIsUpload] = React.useState({ cover: false, thumbnail: false }); const [coverError, setCoverError] = React.useState(false); const [thumbError, setThumbError] = React.useState(false); const { claim_id: claimId } = claim || {}; const [params, setParams]: [any, (any) => void] = React.useState(getChannelParams()); const { channelName } = parseURI(uri); const name = params.name; const isNewChannel = !uri; const { replace } = useHistory(); const languageParam = params.languages; const primaryLanguage = Array.isArray(languageParam) && languageParam.length && languageParam[0]; const secondaryLanguage = Array.isArray(languageParam) && languageParam.length >= 2 && languageParam[1]; const submitLabel = React.useMemo(() => { if (isClaimingInitialRewards) { return __('Claiming credits...'); } return creatingChannel || updatingChannel ? __('Submitting...') : __('Submit'); }, [isClaimingInitialRewards, creatingChannel, updatingChannel]); const submitDisabled = React.useMemo(() => { return ( isClaimingInitialRewards || creatingChannel || updatingChannel || coverError || bidError || (isNewChannel && !params.name) ); }, [isClaimingInitialRewards, creatingChannel, updatingChannel, nameError, bidError, isNewChannel, params.name]); function getChannelParams() { // fill this in with sdk data const channelParams: { website: string, email: string, coverUrl: string, thumbnailUrl: string, description: string, title: string, amount: number, languages: ?Array, locations: ?Array, tags: ?Array<{ name: string }>, claim_id?: string, } = { website, email, coverUrl, thumbnailUrl, description, title, amount: amount || 0.001, languages: languages || [], locations: locations || [], tags: tags ? tags.map((tag) => { return { name: tag }; }) : [], }; if (claimId) { channelParams['claim_id'] = claimId; } return channelParams; } function handleBidChange(bid: number) { const { balance, amount } = props; const totalAvailableBidAmount = (parseFloat(amount) || 0.0) + (parseFloat(balance) || 0.0); setParams({ ...params, amount: bid }); if (bid <= 0.0 || isNaN(bid)) { setBidError(__('Deposit cannot be 0')); } else if (totalAvailableBidAmount < bid) { setBidError( __('Deposit cannot be higher than your available balance: %balance%', { balance: totalAvailableBidAmount }) ); } else if (totalAvailableBidAmount - bid < ESTIMATED_FEE) { setBidError(__('Please decrease your deposit to account for transaction fees')); } else if (bid < MINIMUM_PUBLISH_BID) { setBidError(__('Your deposit must be higher')); } else { setBidError(''); } } function handleLanguageChange(index, code) { let langs = [...languageParam]; if (index === 0) { if (code === LANG_NONE) { // clear all langs = []; } else { langs[0] = code; } } else { if (code === LANG_NONE || code === langs[0]) { langs.splice(1, 1); } else { langs[index] = code; } } setParams({ ...params, languages: langs }); } function handleThumbnailChange(thumbnailUrl: string, uploadSelected: boolean) { setParams({ ...params, thumbnailUrl }); setIsUpload({ ...isUpload, thumbnail: uploadSelected }); setThumbError(false); } function handleCoverChange(coverUrl: string, uploadSelected: boolean) { setParams({ ...params, coverUrl }); setIsUpload({ ...isUpload, cover: uploadSelected }); setCoverError(false); } function handleSubmit() { if (uri) { updateChannel(params).then((success) => { if (success) { onDone(); } }); } else { createChannel(params).then((success) => { if (success) { analytics.apiLogPublish(success); onDone(); } }); } } const LIMIT_ERR_PARTIAL_MSG = 'bad-txns-claimscriptsize-toolarge (code 16)'; let errorMsg = updateError || createError; if (errorMsg && errorMsg.includes(LIMIT_ERR_PARTIAL_MSG)) { errorMsg = __('Transaction limit reached. Try reducing the Description length.'); } if ((!isUpload.thumbnail && thumbError) || (!isUpload.cover && coverError)) { errorMsg = __('Invalid %error_type%', { error_type: (thumbError && 'thumbnail') || (coverError && 'cover image') }); } React.useEffect(() => { let nameError; if (!name && name !== undefined) { nameError = __('A name is required for your url'); } else if (!isNameValid(name)) { nameError = INVALID_NAME_ERROR; } setNameError(nameError); }, [name]); React.useEffect(() => { clearChannelErrors(); }, [clearChannelErrors]); React.useEffect(() => { if (!hasClaimedInitialRewards) { claimInitialRewards(); } }, [hasClaimedInitialRewards, claimInitialRewards]); const coverSrc = coverError ? ThumbnailBrokenImage : params.coverUrl; let thumbnailPreview; if (!params.thumbnailUrl) { thumbnailPreview = Gerbil; } else if (thumbError) { thumbnailPreview = ThumbnailBrokenImage; } else { thumbnailPreview = params.thumbnailUrl; } // TODO clear and bail after submit return ( <>
{params.coverUrl && (coverError && isUpload.cover ? (

{__('Uploaded image will be visible in a few minutes.')}

) : ( setCoverError(true)} /> ))}

{params.title || (channelName && '@' + channelName) || (params.name && '@' + params.name)}

{__('General')} {__('Credit Details')} {__('Tags')} {__('Other')}
@
setParams({ ...params, name: e.target.value })} maxLength={MAX_NAME_LEN} />
{!isNewChannel && {__('This field cannot be changed.')}} setParams({ ...params, title: e.target.value })} maxLength={MAX_TITLE_LEN} /> setParams({ ...params, description: text })} textAreaMaxLength={MAX_DESCRIPTION_LEN} /> } />
} value={params.amount} error={bidError} min="0.0" disabled={false} onChange={(event) => handleBidChange(parseFloat(event.target.value))} placeholder={0.1} helper={ <> {__('Increasing your deposit can help your channel be discovered more easily.')} } /> } /> { const newTags = params.tags.slice().filter((tag) => tag.name !== clickedTag.name); setParams({ ...params, tags: newTags }); }} onSelect={(newTags) => { newTags.forEach((newTag) => { if (!params.tags.map((savedTag) => savedTag.name).includes(newTag.name)) { setParams({ ...params, tags: [...params.tags, newTag] }); } else { // If it already exists and the user types it in, remove it setParams({ ...params, tags: params.tags.filter((tag) => tag.name !== newTag.name) }); } }); }} /> } /> setParams({ ...params, website: e.target.value })} /> setParams({ ...params, email: e.target.value })} /> handleLanguageChange(0, event.target.value)} value={primaryLanguage} helper={__('Your main content language')} > {sortLanguageMap(SUPPORTED_LANGUAGES).map(([langKey, langName]) => ( ))} handleLanguageChange(1, event.target.value)} value={secondaryLanguage} disabled={!languageParam[0]} helper={__('Your other content language')} > {sortLanguageMap(SUPPORTED_LANGUAGES).map(([langKey, langName]) => ( ))} } />
{errorMsg ? ( {errorMsg} ) : (

{__('After submitting, it will take a few minutes for your changes to be live for everyone.')}

)} {!isNewChannel && (
replace(`/$/${PAGES.CHANNELS}`)} />
)} } />
); } export default ChannelForm;