Implements the ChannelForm and ChannelCreate components

This commit is contained in:
Oleg Silkin 2019-11-30 14:39:51 -05:00 committed by Sean Yesmunt
parent b6680fa3e0
commit a87e15754a
4 changed files with 328 additions and 0 deletions

View file

@ -0,0 +1,24 @@
import { connect } from 'react-redux';
import ChannelCreate from './view';
import {
selectBalance,
selectMyChannelClaims,
selectFetchingMyChannels,
doFetchChannelListMine,
doCreateChannel,
} from 'lbry-redux';
import { selectUserVerifiedEmail } from 'lbryinc';
const select = state => ({
channels: selectMyChannelClaims(state),
fetchingChannels: selectFetchingMyChannels(state),
balance: selectBalance(state),
emailVerified: selectUserVerifiedEmail(state),
});
const perform = dispatch => ({
createChannel: (name, amount) => dispatch(doCreateChannel(name, amount)),
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
});
export default connect(select, perform)(ChannelCreate);

View file

@ -0,0 +1,192 @@
// @flow
import React, { Fragment } from 'react';
import { isNameValid } from 'lbry-redux';
import { FormField } from 'component/common/form';
import BusyIndicator from 'component/common/busy-indicator';
import Button from 'component/button';
import analytics from 'analytics';
import { CHANNEL_NEW, MINIMUM_PUBLISH_BID, INVALID_NAME_ERROR } from 'constants/claim';
type Props = {
channel: string, // currently selected channel
channels: ?Array<ChannelClaim>,
balance: number,
onChannelChange: string => void,
createChannel: (string, number) => Promise<any>,
fetchChannelListMine: () => void,
fetchingChannels: boolean,
emailVerified: boolean,
onSuccess: () => void,
};
type State = {
newChannelName: string,
newChannelBid: number,
addingChannel: boolean,
creatingChannel: boolean,
newChannelNameError: string,
newChannelBidError: string,
createChannelError: ?string,
};
class ChannelCreate extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
newChannelName: '',
newChannelBid: 0.1,
addingChannel: false,
creatingChannel: false,
newChannelNameError: '',
newChannelBidError: '',
createChannelError: undefined,
};
(this: any).handleChannelChange = this.handleChannelChange.bind(this);
(this: any).handleNewChannelNameChange = this.handleNewChannelNameChange.bind(this);
(this: any).handleNewChannelBidChange = this.handleNewChannelBidChange.bind(this);
(this: any).handleCreateChannelClick = this.handleCreateChannelClick.bind(this);
}
handleChannelChange(event: SyntheticInputEvent<*>) {
const { onChannelChange } = this.props;
const { newChannelBid } = this.state;
const channel = event.target.value;
if (channel === CHANNEL_NEW) {
this.setState({ addingChannel: true });
onChannelChange(channel);
this.handleNewChannelBidChange(newChannelBid);
} else {
this.setState({ addingChannel: false });
onChannelChange(channel);
}
}
handleNewChannelNameChange(event: SyntheticInputEvent<*>) {
let newChannelName = event.target.value;
if (newChannelName.startsWith('@')) {
newChannelName = newChannelName.slice(1);
}
let newChannelNameError;
if (newChannelName.length > 0 && !isNameValid(newChannelName, false)) {
newChannelNameError = INVALID_NAME_ERROR;
}
this.setState({
newChannelNameError,
newChannelName,
});
}
handleNewChannelBidChange(newChannelBid: number) {
const { balance } = this.props;
let newChannelBidError;
if (newChannelBid === 0) {
newChannelBidError = __('Your deposit cannot be 0');
} else if (newChannelBid === balance) {
newChannelBidError = __('Please decrease your deposit to account for transaction fees');
} else if (newChannelBid > balance) {
newChannelBidError = __('Deposit cannot be higher than your balance');
} else if (newChannelBid < MINIMUM_PUBLISH_BID) {
newChannelBidError = __('Your deposit must be higher');
}
this.setState({
newChannelBid,
newChannelBidError,
});
}
handleCreateChannelClick() {
const { balance, createChannel, onChannelChange } = this.props;
const { newChannelBid, newChannelName } = this.state;
const channelName = `@${newChannelName.trim()}`;
if (newChannelBid > balance) {
return;
}
this.setState({
creatingChannel: true,
createChannelError: undefined,
});
const success = channelClaim => {
this.setState({
creatingChannel: false,
addingChannel: false,
});
analytics.apiLogPublish(channelClaim);
onChannelChange(channelName);
this.props.onSuccess();
};
const failure = () => {
this.setState({
creatingChannel: false,
createChannelError: __('Unable to create channel due to an internal error.'),
});
};
createChannel(channelName, newChannelBid).then(success, failure);
}
render() {
const {
newChannelName,
newChannelNameError,
newChannelBid,
newChannelBidError,
creatingChannel,
createChannelError,
} = this.state;
return (
<Fragment>
{createChannelError && <div className="error-text">{createChannelError}</div>}
<div>
<FormField
label={__('Name')}
name="channel-input"
type="text"
placeholder={__('ChannelName')}
error={newChannelNameError}
value={newChannelName}
onChange={this.handleNewChannelNameChange}
/>
<FormField
className="form-field--price-amount"
name="channel-deposit"
label={__('Deposit (LBC)')}
step="any"
min="0"
type="number"
helper={__('This LBC remains yours. It is a deposit to reserve the name and can be undone at any time.')}
error={newChannelBidError}
value={newChannelBid}
onChange={event => this.handleNewChannelBidChange(parseFloat(event.target.value))}
/>
<div className="card__actions">
<Button
button="primary"
label={!creatingChannel ? __('Create channel') : __('Creating channel...')}
onClick={this.handleCreateChannelClick}
disabled={
!newChannelName || !newChannelBid || creatingChannel || newChannelNameError || newChannelBidError
}
/>
</div>
</div>
{creatingChannel && <BusyIndicator message={`Creating Channel ${newChannelName}...`} />}
</Fragment>
);
}
}
export default ChannelCreate;

View file

@ -0,0 +1,40 @@
import { connect } from 'react-redux';
import {
doResolveUri,
selectPublishFormValues,
selectIsStillEditing,
selectMyClaimForUri,
selectIsResolvingPublishUris,
selectTakeOverAmount,
doResetThumbnailStatus,
doClearPublish,
doUpdatePublishForm,
doPrepareEdit,
} from 'lbry-redux';
import { doPublishDesktop } from 'redux/actions/publish';
import { selectUnclaimedRewardValue } from 'lbryinc';
import ChannelForm from './view';
const select = state => ({
...selectPublishFormValues(state),
// The winning claim for a short lbry uri
amountNeededForTakeover: selectTakeOverAmount(state),
// My previously published claims under this short lbry uri
myClaimForUri: selectMyClaimForUri(state),
// If I clicked the "edit" button, have I changed the uri?
// Need this to make it easier to find the source on previously published content
isStillEditing: selectIsStillEditing(state),
isResolvingUri: selectIsResolvingPublishUris(state),
totalRewardValue: selectUnclaimedRewardValue(state),
});
const perform = dispatch => ({
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
clearPublish: () => dispatch(doClearPublish()),
resolveUri: uri => dispatch(doResolveUri(uri)),
publish: filePath => dispatch(doPublishDesktop(filePath)),
prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)),
resetThumbnailStatus: () => dispatch(doResetThumbnailStatus()),
});
export default connect(select, perform)(ChannelForm);

View file

@ -0,0 +1,72 @@
// @flow
/*
On submit, this component calls publish, which dispatches doPublishDesktop.
doPublishDesktop calls lbry-redux Lbry publish method using lbry-redux publish state as params.
Publish simply instructs the SDK to find the file path on disk and publish it with the provided metadata.
On web, the Lbry publish method call is overridden in platform/web/api-setup, using a function in platform/web/publish.
File upload is carried out in the background by that function.
*/
import React, { useEffect, Fragment } from 'react';
import { CHANNEL_NEW, CHANNEL_ANONYMOUS } from 'constants/claim';
import { buildURI, isURIValid } from 'lbry-redux';
import ChannelCreate from 'component/channelCreate';
import Card from 'component/common/card';
import * as ICONS from 'constants/icons';
type Props = {
name: ?string,
channel: string,
resolveUri: string => void,
// Add back type
updatePublishForm: any => void,
onSuccess: () => void,
};
function ChannelForm(props: Props) {
const { name, channel, resolveUri, updatePublishForm, onSuccess } = props;
// Every time the channel or name changes, resolve the uris to find winning bid amounts
useEffect(() => {
// If they are midway through a channel creation, treat it as anonymous until it completes
const channelName = channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW ? '' : channel;
// We are only going to store the full uri, but we need to resolve the uri with and without the channel name
let uri;
try {
uri = name && buildURI({ streamName: name, channelName });
} catch (e) {}
if (channelName && name) {
// resolve without the channel name so we know the winning bid for it
try {
const uriLessChannel = buildURI({ streamName: name });
resolveUri(uriLessChannel);
} catch (e) {}
}
const isValid = isURIValid(uri);
if (uri && isValid) {
resolveUri(uri);
updatePublishForm({ uri });
}
}, [name, channel, resolveUri, updatePublishForm]);
return (
<Fragment>
<Card
actionIconPadding={false}
icon={ICONS.CHANNEL}
title="Create a New Channel"
subtitle="This is a username or handle that your content can be found under."
actions={
<React.Fragment>
<ChannelCreate onSuccess={onSuccess} onChannelChange={channel => updatePublishForm({ channel })} />
</React.Fragment>
}
/>
</Fragment>
);
}
export default ChannelForm;