Implements the ChannelForm
and ChannelCreate
components
This commit is contained in:
parent
b6680fa3e0
commit
a87e15754a
4 changed files with 328 additions and 0 deletions
24
ui/component/channelCreate/index.js
Normal file
24
ui/component/channelCreate/index.js
Normal 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);
|
192
ui/component/channelCreate/view.jsx
Normal file
192
ui/component/channelCreate/view.jsx
Normal 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;
|
40
ui/component/channelForm/index.js
Normal file
40
ui/component/channelForm/index.js
Normal 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);
|
72
ui/component/channelForm/view.jsx
Normal file
72
ui/component/channelForm/view.jsx
Normal 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;
|
Loading…
Reference in a new issue