new channel creating and editing
This commit is contained in:
parent
c54832c335
commit
36f93343f6
25 changed files with 791 additions and 470 deletions
|
@ -1259,5 +1259,17 @@
|
|||
"Opt out of any topics you don't want to receive email about.": "Opt out of any topics you don't want to receive email about.",
|
||||
"Uncheck your email below if you want to stop receiving messages.": "Uncheck your email below if you want to stop receiving messages.",
|
||||
"Remove from Blocked List": "Remove from Blocked List",
|
||||
"Are you sure you want to remove this from the list?": "Are you sure you want to remove this from the list?"
|
||||
}
|
||||
"Are you sure you want to remove this from the list?": "Are you sure you want to remove this from the list?",
|
||||
"Uncheck your email below if you want to stop receiving messages.": "Uncheck your email below if you want to stop receiving messages.",
|
||||
"Cover": "Cover",
|
||||
"Url": "Url",
|
||||
"New Channel Advanced": "New Channel Advanced",
|
||||
"WTF": "WTF",
|
||||
"required": "required",
|
||||
"A name is required for your url": "A name is required for your url",
|
||||
"Edit Channel": "Edit Channel",
|
||||
"Create Channel": "Create Channel",
|
||||
"This shoul de such a size": "This shoul de such a size",
|
||||
"Thumbnail This shoul de such a size": "Thumbnail This shoul de such a size",
|
||||
"Cover This shoul de such a size": "Cover This shoul de such a size"
|
||||
}
|
||||
|
|
|
@ -31,11 +31,13 @@ function ChannelAbout(props: Props) {
|
|||
<div className="card">
|
||||
<section className="section card--section">
|
||||
<Fragment>
|
||||
<label>{__('Description')}</label>
|
||||
{description && (
|
||||
<div className="media__info-text media__info-text--constrained">
|
||||
<MarkdownPreview content={description} />
|
||||
</div>
|
||||
<>
|
||||
<label>{__('Description')}</label>
|
||||
<div className="media__info-text media__info-text--constrained">
|
||||
<MarkdownPreview content={description} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{email && (
|
||||
<Fragment>
|
||||
|
|
|
@ -3,21 +3,26 @@ import {
|
|||
makeSelectTitleForUri,
|
||||
makeSelectThumbnailForUri,
|
||||
makeSelectCoverForUri,
|
||||
selectCurrentChannelPage,
|
||||
makeSelectMetadataItemForUri,
|
||||
doUpdateChannel,
|
||||
doCreateChannel,
|
||||
makeSelectAmountForUri,
|
||||
makeSelectClaimForUri,
|
||||
selectUpdateChannelError,
|
||||
selectUpdatingChannel,
|
||||
selectCreateChannelError,
|
||||
selectCreatingChannel,
|
||||
selectBalance,
|
||||
} from 'lbry-redux';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
|
||||
import ChannelPage from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
title: makeSelectTitleForUri(props.uri)(state),
|
||||
thumbnailUrl: makeSelectThumbnailForUri(props.uri)(state),
|
||||
coverUrl: makeSelectCoverForUri(props.uri)(state),
|
||||
page: selectCurrentChannelPage(state),
|
||||
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
||||
cover: makeSelectCoverForUri(props.uri)(state),
|
||||
description: makeSelectMetadataItemForUri(props.uri, 'description')(state),
|
||||
website: makeSelectMetadataItemForUri(props.uri, 'website_url')(state),
|
||||
email: makeSelectMetadataItemForUri(props.uri, 'email')(state),
|
||||
|
@ -25,13 +30,20 @@ const select = (state, props) => ({
|
|||
locations: makeSelectMetadataItemForUri(props.uri, 'locations')(state),
|
||||
languages: makeSelectMetadataItemForUri(props.uri, 'languages')(state),
|
||||
amount: makeSelectAmountForUri(props.uri)(state),
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
updateError: selectUpdateChannelError(state),
|
||||
updatingChannel: selectUpdatingChannel(state),
|
||||
createError: selectCreateChannelError(state),
|
||||
creatingChannel: selectCreatingChannel(state),
|
||||
balance: selectBalance(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||
updateChannel: params => dispatch(doUpdateChannel(params)),
|
||||
createChannel: params => {
|
||||
const { name, amount, ...optionalParams } = params;
|
||||
return dispatch(doCreateChannel('@' + name, amount, optionalParams));
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(select, perform)(ChannelPage);
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
// @flow
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { FormField } from 'component/common/form';
|
||||
import Button from 'component/button';
|
||||
import SelectAsset from 'component/selectAsset';
|
||||
import { MINIMUM_PUBLISH_BID } from 'constants/claim';
|
||||
import TagsSearch from 'component/tagsSearch';
|
||||
import { FF_MAX_CHARS_IN_DESCRIPTION } from 'constants/form-field';
|
||||
import ErrorText from 'component/common/error-text';
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import ChannelThumbnail from 'component/channelThumbnail';
|
||||
import { isNameValid, parseURI } from 'lbry-redux';
|
||||
import ClaimAbandonButton from 'component/claimAbandonButton';
|
||||
import { MINIMUM_PUBLISH_BID, INVALID_NAME_ERROR, ESTIMATED_FEE } from 'constants/claim';
|
||||
|
||||
type Props = {
|
||||
claim: ChannelClaim,
|
||||
title: ?string,
|
||||
title: string,
|
||||
amount: string,
|
||||
coverUrl: ?string,
|
||||
thumbnailUrl: ?string,
|
||||
cover: string,
|
||||
thumbnail: string,
|
||||
location: { search: string },
|
||||
description: string,
|
||||
website: string,
|
||||
|
@ -23,45 +27,48 @@ type Props = {
|
|||
locations: Array<string>,
|
||||
languages: Array<string>,
|
||||
updateChannel: any => Promise<any>,
|
||||
updateThumb: string => void,
|
||||
updateCover: string => void,
|
||||
doneEditing: () => void,
|
||||
updateError: string,
|
||||
updatingChannel: boolean,
|
||||
updateError: string,
|
||||
createChannel: any => Promise<any>,
|
||||
createError: string,
|
||||
creatingChannel: boolean,
|
||||
onDone: () => void,
|
||||
openModal: (id: string, { onUpdate: string => void, label: string, helptext: string, currentValue: string }) => void,
|
||||
uri: string,
|
||||
};
|
||||
|
||||
function ChannelForm(props: Props) {
|
||||
const {
|
||||
uri,
|
||||
claim,
|
||||
title,
|
||||
coverUrl,
|
||||
description,
|
||||
website,
|
||||
email,
|
||||
thumbnailUrl,
|
||||
thumbnail,
|
||||
cover,
|
||||
tags,
|
||||
locations,
|
||||
languages,
|
||||
amount,
|
||||
doneEditing,
|
||||
onDone,
|
||||
updateChannel,
|
||||
updateThumb,
|
||||
updateCover,
|
||||
updateError,
|
||||
updatingChannel,
|
||||
createChannel,
|
||||
creatingChannel,
|
||||
createError,
|
||||
openModal,
|
||||
} = props;
|
||||
const { claim_id: claimId } = claim;
|
||||
|
||||
const { claim_id: claimId } = claim || {};
|
||||
// fill this in with sdk data
|
||||
const channelParams = {
|
||||
website,
|
||||
email,
|
||||
coverUrl,
|
||||
thumbnailUrl,
|
||||
cover,
|
||||
thumbnail,
|
||||
description,
|
||||
title,
|
||||
amount,
|
||||
claim_id: claimId,
|
||||
amount: 0.001,
|
||||
languages: languages || [],
|
||||
locations: locations || [],
|
||||
tags: tags
|
||||
|
@ -71,157 +78,241 @@ function ChannelForm(props: Props) {
|
|||
: [],
|
||||
};
|
||||
|
||||
const [params, setParams] = useState(channelParams);
|
||||
if (claimId) {
|
||||
channelParams['claim_id'] = claimId;
|
||||
}
|
||||
|
||||
const { channelName } = parseURI(uri);
|
||||
const [params, setParams]: [any, (any) => void] = useState(channelParams);
|
||||
const [nameError, setNameError] = useState(undefined);
|
||||
const [bidError, setBidError] = useState('');
|
||||
|
||||
const name = params.name;
|
||||
|
||||
useEffect(() => {
|
||||
let nameError;
|
||||
if (!name && name !== undefined) {
|
||||
nameError = __('A name is required for your url');
|
||||
} else if (!isNameValid(name, false)) {
|
||||
nameError = INVALID_NAME_ERROR;
|
||||
}
|
||||
|
||||
setNameError(nameError);
|
||||
}, [name]);
|
||||
// If a user changes tabs, update the url so it stays on the same page if they refresh.
|
||||
// We don't want to use links here because we can't animate the tab change and using links
|
||||
// would alter the Tab label's role attribute, which should stay role="tab" to work with keyboards/screen readers.
|
||||
|
||||
const handleBidChange = (bid: number) => {
|
||||
const { balance, amount } = props;
|
||||
const totalAvailableBidAmount = parseFloat(amount) + parseFloat(balance);
|
||||
const totalAvailableBidAmount = parseFloat(amount) || 0.0 + parseFloat(balance) || 0.0;
|
||||
setParams({ ...params, amount: bid });
|
||||
setBidError('');
|
||||
|
||||
if (bid <= 0.0 || isNaN(bid)) {
|
||||
setBidError(__('Deposit cannot be 0'));
|
||||
} else if (totalAvailableBidAmount === bid) {
|
||||
} else if (totalAvailableBidAmount - bid < ESTIMATED_FEE) {
|
||||
setBidError(__('Please decrease your deposit to account for transaction fees'));
|
||||
} else if (totalAvailableBidAmount < bid) {
|
||||
setBidError(__('Deposit cannot be higher than your balance'));
|
||||
} else if (bid < MINIMUM_PUBLISH_BID) {
|
||||
setBidError(__('Your deposit must be higher'));
|
||||
} else {
|
||||
setBidError('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleThumbnailChange = (thumbnailUrl: string) => {
|
||||
setParams({ ...params, thumbnailUrl });
|
||||
updateThumb(thumbnailUrl);
|
||||
setParams({ ...params, thumbnail: thumbnailUrl });
|
||||
};
|
||||
|
||||
const handleCoverChange = (coverUrl: string) => {
|
||||
setParams({ ...params, coverUrl });
|
||||
updateCover(coverUrl);
|
||||
setParams({ ...params, cover: coverUrl });
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
updateChannel(params).then(success => {
|
||||
if (success) {
|
||||
doneEditing();
|
||||
}
|
||||
});
|
||||
if (uri) {
|
||||
updateChannel(params).then(success => {
|
||||
if (success) {
|
||||
onDone();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
createChannel(params).then(success => {
|
||||
if (success) {
|
||||
onDone();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// TODO clear and bail after submit
|
||||
return (
|
||||
<div className="card">
|
||||
<section className={'section card--section'}>
|
||||
<SelectAsset
|
||||
onUpdate={v => handleThumbnailChange(v)}
|
||||
currentValue={params.thumbnailUrl}
|
||||
assetName={'Thumbnail'}
|
||||
recommended={__('Recommended ratio is 1:1')}
|
||||
/>
|
||||
|
||||
<SelectAsset
|
||||
onUpdate={v => handleCoverChange(v)}
|
||||
currentValue={params.coverUrl}
|
||||
assetName={'Cover'}
|
||||
recommended={__('Recommended ratio is 6.25:1')}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
type="text"
|
||||
name="channel_title2"
|
||||
label={__('Title')}
|
||||
placeholder={__('Titular Title')}
|
||||
disabled={false}
|
||||
value={params.title}
|
||||
onChange={e => setParams({ ...params, title: e.target.value })}
|
||||
/>
|
||||
<FormField
|
||||
className="form-field--price-amount"
|
||||
type="number"
|
||||
name="content_bid2"
|
||||
step="any"
|
||||
label={__('Deposit (LBC)')}
|
||||
postfix="LBC"
|
||||
value={params.amount}
|
||||
error={bidError}
|
||||
min="0.0"
|
||||
disabled={false}
|
||||
onChange={event => handleBidChange(parseFloat(event.target.value))}
|
||||
placeholder={0.1}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
type="text"
|
||||
name="channel_website2"
|
||||
label={__('Website')}
|
||||
placeholder={__('aprettygoodsite.com')}
|
||||
disabled={false}
|
||||
value={params.website}
|
||||
onChange={e => setParams({ ...params, website: e.target.value })}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
type="text"
|
||||
name="content_email2"
|
||||
label={__('Email')}
|
||||
placeholder={__('yourstruly@example.com')}
|
||||
disabled={false}
|
||||
value={params.email}
|
||||
onChange={e => setParams({ ...params, email: e.target.value })}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
type="markdown"
|
||||
name="content_description2"
|
||||
label={__('Description')}
|
||||
placeholder={__('Description of your content')}
|
||||
value={params.description}
|
||||
disabled={false}
|
||||
onChange={text => setParams({ ...params, description: text })}
|
||||
textAreaMaxLength={FF_MAX_CHARS_IN_DESCRIPTION}
|
||||
/>
|
||||
|
||||
<TagsSearch
|
||||
suggestMature
|
||||
disableAutoFocus
|
||||
tagsPassedIn={params.tags || []}
|
||||
label={__('Tags Selected')}
|
||||
onRemove={clickedTag => {
|
||||
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) });
|
||||
<>
|
||||
<div className="main--contained">
|
||||
<header className="channel-cover--edit">
|
||||
<span className={'channel__uri-preview'}>{uri || `lbry://@${params.name || '...'}`}</span>
|
||||
{uri && (
|
||||
<div className="channel__quick-actions">
|
||||
<ClaimAbandonButton uri={uri} />
|
||||
</div>
|
||||
)}
|
||||
<div className="channel__edit-cover">
|
||||
<Button
|
||||
button="alt"
|
||||
title={__('Cover')}
|
||||
onClick={() =>
|
||||
openModal(MODALS.IMAGE_UPLOAD, {
|
||||
onUpdate: v => handleCoverChange(v),
|
||||
label: 'Cover',
|
||||
helptext: 'This shoul de such a size',
|
||||
currentValue: params.cover,
|
||||
})
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div className={'section__actions'}>
|
||||
<Button
|
||||
button="primary"
|
||||
label={updatingChannel ? __('Submitting...') : __('Submit')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<Button button="link" label={__('Cancel')} onClick={doneEditing} />
|
||||
icon={ICONS.CAMERA}
|
||||
iconSize={18}
|
||||
/>
|
||||
</div>
|
||||
{params.cover && <img className="channel-cover__custom" src={params.cover} />}
|
||||
<div className="channel__primary-info">
|
||||
<div className="channel__edit-thumb">
|
||||
<Button
|
||||
button="alt"
|
||||
title={__('Edit')}
|
||||
onClick={() =>
|
||||
openModal(MODALS.IMAGE_UPLOAD, {
|
||||
onUpdate: v => handleThumbnailChange(v),
|
||||
label: 'Thumbnail',
|
||||
helptext: 'This shoul de such a size',
|
||||
currentValue: params.thumbnail,
|
||||
})
|
||||
}
|
||||
icon={ICONS.CAMERA}
|
||||
iconSize={18}
|
||||
/>
|
||||
</div>
|
||||
<ChannelThumbnail
|
||||
className="channel__thumbnail--channel-page"
|
||||
uri={uri}
|
||||
thumbnailPreview={params.thumbnail}
|
||||
allowGifs
|
||||
/>
|
||||
<h1 className="channel__title">
|
||||
{params.title || (channelName && '@' + channelName) || (params.name && '@' + params.name)}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="channel-cover__gradient" />
|
||||
</header>
|
||||
<div className="card">
|
||||
<section className={'section card--section'}>
|
||||
{!uri && (
|
||||
<FormField
|
||||
type="text"
|
||||
name="channel_name"
|
||||
label={__('Name')}
|
||||
placeholder={__('required')}
|
||||
disabled={false}
|
||||
value={params.name}
|
||||
error={nameError}
|
||||
onChange={e => setParams({ ...params, name: e.target.value })}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
className="form-field--price-amount"
|
||||
type="number"
|
||||
name="content_bid2"
|
||||
step="any"
|
||||
label={__('Deposit (LBC)')}
|
||||
postfix="LBC"
|
||||
value={params.amount}
|
||||
error={bidError}
|
||||
min="0.0"
|
||||
disabled={false}
|
||||
onChange={event => handleBidChange(parseFloat(event.target.value))}
|
||||
placeholder={0.1}
|
||||
/>
|
||||
<FormField
|
||||
type="text"
|
||||
name="channel_title2"
|
||||
label={__('Title')}
|
||||
placeholder={__('Titular Title')}
|
||||
disabled={false}
|
||||
value={params.title}
|
||||
onChange={e => setParams({ ...params, title: e.target.value })}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
type="text"
|
||||
name="channel_website2"
|
||||
label={__('Website')}
|
||||
placeholder={__('aprettygoodsite.com')}
|
||||
disabled={false}
|
||||
value={params.website}
|
||||
onChange={e => setParams({ ...params, website: e.target.value })}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
type="text"
|
||||
name="content_email2"
|
||||
label={__('Email')}
|
||||
placeholder={__('yourstruly@example.com')}
|
||||
disabled={false}
|
||||
value={params.email}
|
||||
onChange={e => setParams({ ...params, email: e.target.value })}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
type="markdown"
|
||||
name="content_description2"
|
||||
label={__('Description')}
|
||||
placeholder={__('Description of your content')}
|
||||
value={params.description}
|
||||
disabled={false}
|
||||
onChange={text => setParams({ ...params, description: text })}
|
||||
textAreaMaxLength={FF_MAX_CHARS_IN_DESCRIPTION}
|
||||
/>
|
||||
<label>{__('Tags')}</label>
|
||||
<div className="tags__border">
|
||||
<TagsSearch
|
||||
suggestMature
|
||||
disableAutoFocus
|
||||
tagsPassedIn={params.tags || []}
|
||||
label={__('Selected Tags')}
|
||||
onRemove={clickedTag => {
|
||||
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) });
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={'section__actions'}>
|
||||
<Button
|
||||
button="primary"
|
||||
label={creatingChannel || updatingChannel ? __('Submitting') : __('Submit')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<Button button="link" label={__('Cancel')} onClick={onDone} />
|
||||
</div>
|
||||
{updateError || createError ? (
|
||||
<ErrorText>{updateError || createError}</ErrorText>
|
||||
) : (
|
||||
<p className="help">
|
||||
{__('After submitting, you will not see the changes immediately. Please check back in a few minutes.')}
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
{updateError && updateError.length ? (
|
||||
<ErrorText>{updateError}</ErrorText>
|
||||
) : (
|
||||
<p className="help">
|
||||
{__('After submitting, you will not see the changes immediately. Please check back in a few minutes.')}
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -662,7 +662,9 @@ export const icons = {
|
|||
<path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" />
|
||||
</g>
|
||||
),
|
||||
[ICONS.OPEN_LOG_FOLDER]: buildIcon(<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />),
|
||||
[ICONS.OPEN_LOG_FOLDER]: buildIcon(
|
||||
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
|
||||
),
|
||||
[ICONS.OPEN_LOG]: buildIcon(
|
||||
<g>
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||
|
@ -671,4 +673,10 @@ export const icons = {
|
|||
<line x1="9" y1="15" x2="15" y2="15" />
|
||||
</g>
|
||||
),
|
||||
[ICONS.CAMERA]: buildIcon(
|
||||
<g>
|
||||
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" />
|
||||
<circle cx="12" cy="13" r="4" />
|
||||
</g>
|
||||
),
|
||||
};
|
||||
|
|
|
@ -37,6 +37,7 @@ type Props = {
|
|||
email: ?string,
|
||||
authenticated: boolean,
|
||||
authHeader: boolean,
|
||||
backout: { backFunction: () => void, backTitle: string },
|
||||
syncError: ?string,
|
||||
emailToVerify?: string,
|
||||
signOut: () => void,
|
||||
|
@ -66,6 +67,7 @@ const Header = (props: Props) => {
|
|||
clearEmailEntry,
|
||||
clearPasswordEntry,
|
||||
emailToVerify,
|
||||
backout,
|
||||
} = props;
|
||||
|
||||
// on the verify page don't let anyone escape other than by closing the tab to keep session data consistent
|
||||
|
@ -135,201 +137,221 @@ const Header = (props: Props) => {
|
|||
// @endif
|
||||
>
|
||||
<div className="header__contents">
|
||||
<div className="header__navigation">
|
||||
<Button
|
||||
className="header__navigation-item header__navigation-item--lbry header__navigation-item--button-mobile"
|
||||
label={LOGO_TITLE}
|
||||
icon={ICONS.LBRY}
|
||||
onClick={() => {
|
||||
if (history.location.pathname === '/') window.location.reload();
|
||||
}}
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
{...homeButtonNavigationProps}
|
||||
/>
|
||||
|
||||
{/* @if TARGET='app' */}
|
||||
{!authHeader && (
|
||||
<div className="header__navigation-arrows">
|
||||
<NavigationButton isBackward history={history} />
|
||||
<NavigationButton isBackward={false} history={history} />
|
||||
</div>
|
||||
)}
|
||||
{/* @endif */}
|
||||
|
||||
{!authHeader && <WunderBar />}
|
||||
</div>
|
||||
|
||||
{!authHeader ? (
|
||||
<div className={classnames('header__menu', { 'header__menu--with-balance': !IS_WEB || authenticated })}>
|
||||
{(!IS_WEB || authenticated) && (
|
||||
<Fragment>
|
||||
<Button
|
||||
aria-label={__('Your wallet')}
|
||||
navigate={`/$/${PAGES.WALLET}`}
|
||||
className="header__navigation-item menu__title header__navigation-item--balance"
|
||||
label={getWalletTitle()}
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
/>
|
||||
<Menu>
|
||||
<MenuButton
|
||||
aria-label={__('Publish a file, or create a channel')}
|
||||
title={__('Publish a file, or create a channel')}
|
||||
className="header__navigation-item menu__title header__navigation-item--icon"
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
>
|
||||
<Icon size={18} icon={ICONS.PUBLISH} aria-hidden />
|
||||
</MenuButton>
|
||||
<MenuList className="menu__list--header">
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.PUBLISH}`)}>
|
||||
<Icon aria-hidden icon={ICONS.PUBLISH} />
|
||||
{__('Publish')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={openChannelCreate}>
|
||||
<Icon aria-hidden icon={ICONS.CHANNEL} />
|
||||
{__('New Channel')}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
||||
<Menu>
|
||||
<MenuButton
|
||||
aria-label={__('Your account')}
|
||||
title={__('Your account')}
|
||||
className="header__navigation-item menu__title header__navigation-item--icon"
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
>
|
||||
<Icon size={18} icon={ICONS.ACCOUNT} aria-hidden />
|
||||
</MenuButton>
|
||||
<MenuList className="menu__list--header">
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.PUBLISHED}`)}>
|
||||
<Icon aria-hidden icon={ICONS.PUBLISH} />
|
||||
{__('Publishes')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.CHANNELS}`)}>
|
||||
<Icon aria-hidden icon={ICONS.CHANNEL} />
|
||||
{__('Channels')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.CREATOR_DASHBOARD}`)}>
|
||||
<Icon aria-hidden icon={ICONS.ANALYTICS} />
|
||||
{__('Creator Analytics')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.REWARDS}`)}>
|
||||
<Icon aria-hidden icon={ICONS.REWARDS} />
|
||||
{__('Rewards')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.INVITE}`)}>
|
||||
<Icon aria-hidden icon={ICONS.INVITE} />
|
||||
{__('Invites')}
|
||||
</MenuItem>
|
||||
|
||||
{authenticated ? (
|
||||
<MenuItem onSelect={IS_WEB ? signOut : openSignOutModal}>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden icon={ICONS.SIGN_OUT} />
|
||||
{__('Sign Out')}
|
||||
</div>
|
||||
<span className="menu__link-help">{email}</span>
|
||||
</MenuItem>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.AUTH}`)}>
|
||||
<Icon aria-hidden icon={ICONS.SIGN_UP} />
|
||||
{__('Register')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.AUTH_SIGNIN}`)}>
|
||||
<Icon aria-hidden icon={ICONS.SIGN_IN} />
|
||||
{__('Sign In')}
|
||||
</MenuItem>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Fragment>
|
||||
)}
|
||||
<Menu>
|
||||
<MenuButton
|
||||
aria-label={__('Settings')}
|
||||
title={__('Settings')}
|
||||
className="header__navigation-item menu__title header__navigation-item--icon"
|
||||
{!authHeader && backout ? (
|
||||
<div className="header__contents--between">
|
||||
<Button onClick={backout.backFunction} button="link" label={__('Back')} />
|
||||
{backout.backTitle && <h1 className={'card__title'}>{backout.backTitle}</h1>}
|
||||
<Button
|
||||
aria-label={__('Your wallet')}
|
||||
navigate={`/$/${PAGES.WALLET}`}
|
||||
className="header__navigation-item menu__title header__navigation-item--balance"
|
||||
label={getWalletTitle()}
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="header__navigation">
|
||||
<Button
|
||||
className="header__navigation-item header__navigation-item--lbry header__navigation-item--button-mobile"
|
||||
label={LOGO_TITLE}
|
||||
icon={ICONS.LBRY}
|
||||
onClick={() => {
|
||||
if (history.location.pathname === '/') window.location.reload();
|
||||
}}
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
>
|
||||
<Icon size={18} icon={ICONS.SETTINGS} aria-hidden />
|
||||
</MenuButton>
|
||||
<MenuList className="menu__list--header">
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.SETTINGS}`)}>
|
||||
<Icon aria-hidden tootlip icon={ICONS.SETTINGS} />
|
||||
{__('Settings')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.HELP}`)}>
|
||||
<Icon aria-hidden icon={ICONS.HELP} />
|
||||
{__('Help')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={handleThemeToggle}>
|
||||
<Icon icon={currentTheme === 'light' ? ICONS.DARK : ICONS.LIGHT} />
|
||||
{currentTheme === 'light' ? __('Dark') : __('Light')}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
{IS_WEB && !authenticated && (
|
||||
<div className="header__auth-buttons">
|
||||
<Button navigate={`/$/${PAGES.AUTH_SIGNIN}`} button="link" label={__('Sign In')} />
|
||||
<Button navigate={`/$/${PAGES.AUTH}`} button="primary" label={__('Register')} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
!isVerifyPage && (
|
||||
<div className="header__menu">
|
||||
{/* Add an empty span here so we can use the same style as above */}
|
||||
{/* This pushes the close button to the right side */}
|
||||
<span />
|
||||
<Tooltip label={__('Go Back')}>
|
||||
<Button
|
||||
button="alt"
|
||||
// className="button--header-close"
|
||||
icon={ICONS.REMOVE}
|
||||
{...closeButtonNavigationProps}
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
/>
|
||||
</Tooltip>
|
||||
{...homeButtonNavigationProps}
|
||||
/>
|
||||
|
||||
{/* @if TARGET='app' */}
|
||||
{!authHeader && (
|
||||
<div className="header__navigation-arrows">
|
||||
<NavigationButton isBackward history={history} />
|
||||
<NavigationButton isBackward={false} history={history} />
|
||||
</div>
|
||||
)}
|
||||
{/* @endif */}
|
||||
|
||||
{!authHeader && <WunderBar />}
|
||||
</div>
|
||||
)
|
||||
|
||||
{!authHeader ? (
|
||||
<div className={classnames('header__menu', { 'header__menu--with-balance': !IS_WEB || authenticated })}>
|
||||
{(!IS_WEB || authenticated) && (
|
||||
<Fragment>
|
||||
<Button
|
||||
aria-label={__('Your wallet')}
|
||||
navigate={`/$/${PAGES.WALLET}`}
|
||||
className="header__navigation-item menu__title header__navigation-item--balance"
|
||||
label={getWalletTitle()}
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
/>
|
||||
<Menu>
|
||||
<MenuButton
|
||||
aria-label={__('Publish a file, or create a channel')}
|
||||
title={__('Publish a file, or create a channel')}
|
||||
className="header__navigation-item menu__title header__navigation-item--icon"
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
>
|
||||
<Icon size={18} icon={ICONS.PUBLISH} aria-hidden />
|
||||
</MenuButton>
|
||||
<MenuList className="menu__list--header">
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.PUBLISH}`)}>
|
||||
<Icon aria-hidden icon={ICONS.PUBLISH} />
|
||||
{__('Publish')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={openChannelCreate}>
|
||||
<Icon aria-hidden icon={ICONS.CHANNEL} />
|
||||
{__('New Channel')}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
||||
<Menu>
|
||||
<MenuButton
|
||||
aria-label={__('Your account')}
|
||||
title={__('Your account')}
|
||||
className="header__navigation-item menu__title header__navigation-item--icon"
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
>
|
||||
<Icon size={18} icon={ICONS.ACCOUNT} aria-hidden />
|
||||
</MenuButton>
|
||||
<MenuList className="menu__list--header">
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.PUBLISHED}`)}>
|
||||
<Icon aria-hidden icon={ICONS.PUBLISH} />
|
||||
{__('Publishes')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.CHANNELS}`)}>
|
||||
<Icon aria-hidden icon={ICONS.CHANNEL} />
|
||||
{__('Channels')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.CREATOR_DASHBOARD}`)}>
|
||||
<Icon aria-hidden icon={ICONS.ANALYTICS} />
|
||||
{__('Creator Analytics')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.REWARDS}`)}>
|
||||
<Icon aria-hidden icon={ICONS.REWARDS} />
|
||||
{__('Rewards')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.INVITE}`)}>
|
||||
<Icon aria-hidden icon={ICONS.INVITE} />
|
||||
{__('Invites')}
|
||||
</MenuItem>
|
||||
|
||||
{authenticated ? (
|
||||
<MenuItem onSelect={IS_WEB ? signOut : openSignOutModal}>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden icon={ICONS.SIGN_OUT} />
|
||||
{__('Sign Out')}
|
||||
</div>
|
||||
<span className="menu__link-help">{email}</span>
|
||||
</MenuItem>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.AUTH}`)}>
|
||||
<Icon aria-hidden icon={ICONS.SIGN_UP} />
|
||||
{__('Register')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.AUTH_SIGNIN}`)}>
|
||||
<Icon aria-hidden icon={ICONS.SIGN_IN} />
|
||||
{__('Sign In')}
|
||||
</MenuItem>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Fragment>
|
||||
)}
|
||||
<Menu>
|
||||
<MenuButton
|
||||
aria-label={__('Settings')}
|
||||
title={__('Settings')}
|
||||
className="header__navigation-item menu__title header__navigation-item--icon"
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
>
|
||||
<Icon size={18} icon={ICONS.SETTINGS} aria-hidden />
|
||||
</MenuButton>
|
||||
<MenuList className="menu__list--header">
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.SETTINGS}`)}>
|
||||
<Icon aria-hidden tootlip icon={ICONS.SETTINGS} />
|
||||
{__('Settings')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.HELP}`)}>
|
||||
<Icon aria-hidden icon={ICONS.HELP} />
|
||||
{__('Help')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={handleThemeToggle}>
|
||||
<Icon icon={currentTheme === 'light' ? ICONS.DARK : ICONS.LIGHT} />
|
||||
{currentTheme === 'light' ? __('Dark') : __('Light')}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
{IS_WEB && !authenticated && (
|
||||
<div className="header__auth-buttons">
|
||||
<Button navigate={`/$/${PAGES.AUTH_SIGNIN}`} button="link" label={__('Sign In')} />
|
||||
<Button navigate={`/$/${PAGES.AUTH}`} button="primary" label={__('Register')} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
!isVerifyPage && (
|
||||
<div className="header__menu">
|
||||
{/* Add an empty span here so we can use the same style as above */}
|
||||
{/* This pushes the close button to the right side */}
|
||||
<span />
|
||||
<Tooltip label={__('Go Back')}>
|
||||
<Button
|
||||
button="alt"
|
||||
// className="button--header-close"
|
||||
icon={ICONS.REMOVE}
|
||||
{...closeButtonNavigationProps}
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
<Button
|
||||
onClick={openMobileNavigation}
|
||||
icon={ICONS.MENU}
|
||||
iconSize={24}
|
||||
className="header__menu--mobile"
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
onClick={openMobileNavigation}
|
||||
icon={ICONS.MENU}
|
||||
iconSize={24}
|
||||
className="header__menu--mobile"
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
|
|
@ -19,14 +19,23 @@ type Props = {
|
|||
noHeader: boolean,
|
||||
noFooter: boolean,
|
||||
noSideNavigation: boolean,
|
||||
backout: { backFunction: () => void, backTitle: string },
|
||||
};
|
||||
|
||||
function Page(props: Props) {
|
||||
const { children, className, authPage = false, noHeader = false, noFooter = false, noSideNavigation = false } = props;
|
||||
const {
|
||||
children,
|
||||
className,
|
||||
authPage = false,
|
||||
noHeader = false,
|
||||
noFooter = false,
|
||||
noSideNavigation = false,
|
||||
backout,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{!noHeader && <Header authHeader={authPage} />}
|
||||
{!noHeader && <Header authHeader={authPage} backout={backout} />}
|
||||
<div className={classnames('main-wrapper__inner')}>
|
||||
<main className={classnames(MAIN_CLASS, className, { 'main--full-width': authPage })}>{children}</main>
|
||||
{!authPage && !noSideNavigation && <SideNavigation />}
|
||||
|
|
|
@ -40,6 +40,7 @@ import Welcome from 'page/welcome';
|
|||
import CreatorDashboard from 'page/creatorDashboard';
|
||||
import RewardsVerifyPage from 'page/rewardsVerify';
|
||||
import CheckoutPage from 'page/checkoutPage';
|
||||
import ChannelNew from 'page/channelNew';
|
||||
import BuyPage from 'page/buy';
|
||||
|
||||
import { parseURI } from 'lbry-redux';
|
||||
|
@ -188,6 +189,7 @@ function AppRouter(props: Props) {
|
|||
<Route path={`/$/${PAGES.CHECKOUT}`} exact component={CheckoutPage} />
|
||||
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.INVITE}`} component={InvitePage} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.CHANNEL_NEW}`} component={ChannelNew} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.PUBLISHED}`} component={FileListPublished} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.CREATOR_DASHBOARD}`} component={CreatorDashboard} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.PUBLISH}`} component={PublishPage} />
|
||||
|
|
|
@ -6,13 +6,14 @@ import FileSelector from 'component/common/file-selector';
|
|||
import Button from 'component/button';
|
||||
import { SPEECH_URLS } from 'lbry-redux';
|
||||
import uuid from 'uuid/v4';
|
||||
import { Tab, TabList, TabPanel, TabPanels, Tabs } from '../common/tabs';
|
||||
|
||||
const accept = '.png, .jpg, .jpeg, .gif';
|
||||
|
||||
const SOURCE_URL = 'url';
|
||||
const SOURCE_UPLOAD = 'upload';
|
||||
const SPEECH_READY = 'READY';
|
||||
const SPEECH_UPLOADING = 'UPLOADING';
|
||||
|
||||
const URL_INDEX = 0;
|
||||
|
||||
type Props = {
|
||||
assetName: string,
|
||||
currentValue: ?string,
|
||||
|
@ -22,11 +23,12 @@ type Props = {
|
|||
|
||||
function SelectAsset(props: Props) {
|
||||
const { onUpdate, assetName, currentValue, recommended } = props;
|
||||
const [assetSource, setAssetSource] = useState(SOURCE_URL);
|
||||
const [asset, setAsset] = useState(currentValue);
|
||||
const [pathSelected, setPathSelected] = useState('');
|
||||
const [fileSelected, setFileSelected] = useState<any>(null);
|
||||
const [uploadStatus, setUploadStatus] = useState(SPEECH_READY);
|
||||
const [error, setError] = useState();
|
||||
const [tabIndex, setTabIndex] = useState(URL_INDEX);
|
||||
|
||||
function doUploadAsset(file) {
|
||||
const uploadError = (error = '') => {
|
||||
|
@ -36,7 +38,8 @@ function SelectAsset(props: Props) {
|
|||
const setUrl = path => {
|
||||
setUploadStatus(SPEECH_READY);
|
||||
onUpdate(path);
|
||||
setAssetSource(SOURCE_URL);
|
||||
setAsset(path);
|
||||
setTabIndex(URL_INDEX);
|
||||
};
|
||||
|
||||
setUploadStatus(SPEECH_UPLOADING);
|
||||
|
@ -57,71 +60,73 @@ function SelectAsset(props: Props) {
|
|||
|
||||
return (
|
||||
<fieldset-section>
|
||||
<fieldset-group className="fieldset-group--smushed">
|
||||
<FormField
|
||||
type="select"
|
||||
name={assetName}
|
||||
value={assetSource}
|
||||
onChange={e => setAssetSource(e.target.value)}
|
||||
label={__(assetName + ' source')}
|
||||
>
|
||||
<option key={'lmmnop'} value={'url'}>
|
||||
URL
|
||||
</option>
|
||||
<option key={'lmmnopq'} value={'upload'}>
|
||||
UPLOAD
|
||||
</option>
|
||||
</FormField>
|
||||
{assetSource === SOURCE_UPLOAD && (
|
||||
<div>
|
||||
{error && <div className="error__text">{error}</div>}
|
||||
{!pathSelected && (
|
||||
<FileSelector
|
||||
label={'File to upload'}
|
||||
name={'assetSelector'}
|
||||
onFileChosen={file => {
|
||||
if (file.name) {
|
||||
setPathSelected(file.path || file.name);
|
||||
setFileSelected(file);
|
||||
}
|
||||
}}
|
||||
accept={accept}
|
||||
/>
|
||||
)}
|
||||
{pathSelected && (
|
||||
<div>
|
||||
{`...${pathSelected.slice(-18)}`} {uploadStatus}{' '}
|
||||
<Button button={'primary'} onClick={() => doUploadAsset(fileSelected)}>
|
||||
Upload
|
||||
</Button>{' '}
|
||||
<Button
|
||||
button={'secondary'}
|
||||
onClick={() => {
|
||||
setPathSelected('');
|
||||
setFileSelected(null);
|
||||
setError(null);
|
||||
<Tabs onChange={n => setTabIndex(n)} index={tabIndex}>
|
||||
<TabList className="tabs__list--select-asset">
|
||||
<Tab>{__('Url')}</Tab>
|
||||
<Tab>{__('Upload')}</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<FormField
|
||||
type={'text'}
|
||||
name={'thumbnail'}
|
||||
label={__(assetName + ' ' + recommended)}
|
||||
placeholder={'https://example.com/image.png'}
|
||||
disabled={false}
|
||||
value={asset}
|
||||
onChange={e => {
|
||||
onUpdate(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<div>
|
||||
{error && <div className="error__text">{error}</div>}
|
||||
{!pathSelected && (
|
||||
<FileSelector
|
||||
label={'File to upload'}
|
||||
name={'assetSelector'}
|
||||
onFileChosen={file => {
|
||||
if (file.name) {
|
||||
setPathSelected(file.path || file.name);
|
||||
setFileSelected(file);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{assetSource === SOURCE_URL && (
|
||||
<FormField
|
||||
type={'text'}
|
||||
name={'thumbnail'}
|
||||
label={__(assetName + ' ' + recommended)}
|
||||
placeholder={'https://example.com/image.png'}
|
||||
disabled={false}
|
||||
value={currentValue}
|
||||
onChange={e => {
|
||||
onUpdate(e.target.value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</fieldset-group>
|
||||
accept={accept}
|
||||
/>
|
||||
)}
|
||||
{pathSelected && (
|
||||
<div>
|
||||
<FormField
|
||||
type={'text'}
|
||||
name={'uploaded_thumbnail'}
|
||||
label={''}
|
||||
placeholder={'https://example.com/image.png'}
|
||||
disabled={false}
|
||||
value={`${pathSelected}`}
|
||||
/>
|
||||
<div>
|
||||
<Button button={'primary'} onClick={() => doUploadAsset(fileSelected)}>
|
||||
Upload
|
||||
</Button>
|
||||
<Button
|
||||
button={'secondary'}
|
||||
onClick={() => {
|
||||
setPathSelected('');
|
||||
setFileSelected(null);
|
||||
setError(null);
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
{uploadStatus}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</fieldset-section>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export const MINIMUM_PUBLISH_BID = 0.0001;
|
||||
export const ESTIMATED_FEE = 0.048; // .001 + .001 | .048 + .048 = .1
|
||||
|
||||
export const CHANNEL_ANONYMOUS = 'anonymous';
|
||||
export const CHANNEL_NEW = 'new';
|
||||
|
|
|
@ -105,5 +105,6 @@ export const PINNED = 'Pinned';
|
|||
export const BUY = 'Buy';
|
||||
export const SEND = 'Send';
|
||||
export const RECEIVE = 'Receive';
|
||||
export const CAMERA = 'Camera';
|
||||
export const OPEN_LOG = 'FilePlus';
|
||||
export const OPEN_LOG_FOLDER = 'Folder';
|
||||
|
|
|
@ -42,3 +42,4 @@ export const SIGN_OUT = 'sign_out';
|
|||
export const LIQUIDATE_SUPPORTS = 'liquidate_supports';
|
||||
export const CONFIRM_AGE = 'confirm_age';
|
||||
export const REMOVE_BLOCKED = 'remove_blocked';
|
||||
export const IMAGE_UPLOAD = 'image_upload';
|
||||
|
|
|
@ -39,3 +39,4 @@ exports.CREATOR_DASHBOARD = 'dashboard';
|
|||
exports.CHECKOUT = 'checkout';
|
||||
exports.CODE_2257 = '2257';
|
||||
exports.BUY = 'buy';
|
||||
exports.CHANNEL_NEW = 'channelnew';
|
||||
|
|
11
ui/modal/modalImageUpload/index.js
Normal file
11
ui/modal/modalImageUpload/index.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doHideModal } from 'redux/actions/app';
|
||||
import ModalImageUpload from './view';
|
||||
|
||||
const perform = dispatch => () => ({
|
||||
closeModal: () => {
|
||||
dispatch(doHideModal());
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(null, perform)(ModalImageUpload);
|
41
ui/modal/modalImageUpload/view.jsx
Normal file
41
ui/modal/modalImageUpload/view.jsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { Modal } from 'modal/modal';
|
||||
import Button from 'component/button';
|
||||
import Card from 'component/common/card';
|
||||
import SelectAsset from 'component/selectAsset';
|
||||
|
||||
type Props = {
|
||||
closeModal: () => void,
|
||||
currentValue: string,
|
||||
label: string,
|
||||
helptext: string,
|
||||
onUpdate: string => void,
|
||||
};
|
||||
|
||||
const ModalImageUpload = (props: Props) => {
|
||||
const { closeModal, currentValue, label, helptext, onUpdate } = props;
|
||||
|
||||
return (
|
||||
<Modal isOpen type="card" onAborted={closeModal}>
|
||||
<Card
|
||||
title={__(label)}
|
||||
body={
|
||||
<SelectAsset
|
||||
onUpdate={v => onUpdate(v)}
|
||||
currentValue={currentValue}
|
||||
assetName={label}
|
||||
recommended={__(helptext)}
|
||||
/>
|
||||
}
|
||||
actions={
|
||||
<div className="card__actions">
|
||||
<Button button="primary" label={__('Done')} onClick={closeModal} />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalImageUpload;
|
|
@ -40,6 +40,7 @@ import ModalSignOut from 'modal/modalSignOut';
|
|||
import ModalSupportsLiquidate from 'modal/modalSupportsLiquidate';
|
||||
import ModalConfirmAge from 'modal/modalConfirmAge';
|
||||
import ModalFileSelection from 'modal/modalFileSelection';
|
||||
import ModalImageUpload from 'modal/modalImageUpload';
|
||||
|
||||
type Props = {
|
||||
modal: { id: string, modalProps: {} },
|
||||
|
@ -143,6 +144,8 @@ function ModalRouter(props: Props) {
|
|||
return <ModalSupportsLiquidate {...modalProps} />;
|
||||
case MODALS.REMOVE_BLOCKED:
|
||||
return <ModalRemoveBlocked {...modalProps} />;
|
||||
case MODALS.IMAGE_UPLOAD:
|
||||
return <ModalImageUpload {...modalProps} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
} from 'lbry-redux';
|
||||
import { selectBlackListedOutpoints, doFetchSubCount, makeSelectSubCountForUri } from 'lbryinc';
|
||||
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import ChannelPage from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
@ -28,6 +29,7 @@ const select = (state, props) => ({
|
|||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||
fetchSubCount: claimId => dispatch(doFetchSubCount(claimId)),
|
||||
});
|
||||
|
||||
|
|
|
@ -52,14 +52,13 @@ type Props = {
|
|||
function ChannelPage(props: Props) {
|
||||
const {
|
||||
uri,
|
||||
claim,
|
||||
title,
|
||||
cover,
|
||||
history,
|
||||
location,
|
||||
page,
|
||||
channelIsMine,
|
||||
thumbnail,
|
||||
claim,
|
||||
isSubscribed,
|
||||
channelIsBlocked,
|
||||
blackListedOutpoints,
|
||||
|
@ -72,11 +71,8 @@ function ChannelPage(props: Props) {
|
|||
const { search } = location;
|
||||
const urlParams = new URLSearchParams(search);
|
||||
const currentView = urlParams.get(PAGE_VIEW_QUERY) || undefined;
|
||||
const [coverError, setCoverError] = useState(false);
|
||||
const { permanent_url: permanentUrl } = claim;
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [thumbPreview, setThumbPreview] = useState(thumbnail);
|
||||
const [coverPreview, setCoverPreview] = useState(cover);
|
||||
const [lastYtSyncDate, setLastYtSyncDate] = useState();
|
||||
const claimId = claim.claim_id;
|
||||
const formattedSubCount = Number(subCount).toLocaleString();
|
||||
|
@ -100,18 +96,14 @@ function ChannelPage(props: Props) {
|
|||
history.push(`${url}${search}`);
|
||||
}
|
||||
|
||||
function doneEditing() {
|
||||
function onDone() {
|
||||
setEditing(false);
|
||||
setThumbPreview(thumbnail);
|
||||
setCoverPreview(cover);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
Lbryio.call('yt', 'get_youtuber', { channel_claim_id: claimId }).then(response => {
|
||||
if (response.is_verified_youtuber) {
|
||||
setLastYtSyncDate(response.last_synced);
|
||||
} else {
|
||||
setLastYtSyncDate(undefined);
|
||||
}
|
||||
});
|
||||
}, [claimId]);
|
||||
|
@ -134,6 +126,19 @@ function ChannelPage(props: Props) {
|
|||
}
|
||||
}, [channelIsMine, editing]);
|
||||
|
||||
if (editing) {
|
||||
return (
|
||||
<Page
|
||||
noFooter
|
||||
noSideNavigation={editing}
|
||||
title={__('Edit Channel')}
|
||||
backout={{ backFunction: onDone, backTitle: __('Edit Channel') }}
|
||||
>
|
||||
<ChannelEdit uri={uri} onDone={onDone} />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Page noFooter>
|
||||
<ClaimUri uri={uri} />
|
||||
|
@ -153,39 +158,26 @@ function ChannelPage(props: Props) {
|
|||
{!channelIsBlocked && (!channelIsBlackListed || isSubscribed) && <SubscribeButton uri={permanentUrl} />}
|
||||
{!isSubscribed && <BlockButton uri={permanentUrl} />}
|
||||
</div>
|
||||
{!editing && cover && !coverError && (
|
||||
{cover && (
|
||||
<img
|
||||
className={classnames('channel-cover__custom', { 'channel__image--blurred': channelIsBlocked })}
|
||||
src={cover}
|
||||
onError={() => setCoverError(true)}
|
||||
/>
|
||||
)}
|
||||
{editing && <img className="channel-cover__custom" src={coverPreview} />}
|
||||
{/* component that offers select/upload */}
|
||||
<div className="channel__primary-info">
|
||||
{!editing && (
|
||||
<ChannelThumbnail
|
||||
className="channel__thumbnail--channel-page"
|
||||
uri={uri}
|
||||
obscure={channelIsBlocked}
|
||||
allowGifs
|
||||
/>
|
||||
)}
|
||||
{editing && (
|
||||
<ChannelThumbnail
|
||||
className="channel__thumbnail--channel-page"
|
||||
uri={uri}
|
||||
thumbnailPreview={thumbPreview}
|
||||
allowGifs
|
||||
/>
|
||||
)}
|
||||
<ChannelThumbnail
|
||||
className="channel__thumbnail--channel-page"
|
||||
uri={uri}
|
||||
obscure={channelIsBlocked}
|
||||
allowGifs
|
||||
/>
|
||||
<h1 className="channel__title">{title || '@' + channelName}</h1>
|
||||
<div className="channel__meta">
|
||||
<span>
|
||||
{formattedSubCount} {subCount !== 1 ? __('Followers') : __('Follower')}
|
||||
<HelpLink href="https://lbry.com/faq/views" />
|
||||
</span>
|
||||
{channelIsMine && !editing && (
|
||||
{channelIsMine && (
|
||||
<>
|
||||
{pending ? (
|
||||
<span>{__('Your changes will be live in a few minutes')}</span>
|
||||
|
@ -201,15 +193,6 @@ function ChannelPage(props: Props) {
|
|||
)}
|
||||
</>
|
||||
)}
|
||||
{channelIsMine && editing && (
|
||||
<Button
|
||||
button="alt"
|
||||
title={__('Cancel')}
|
||||
onClick={() => doneEditing()}
|
||||
icon={ICONS.REMOVE}
|
||||
iconSize={18}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="channel-cover__gradient" />
|
||||
|
@ -220,22 +203,12 @@ function ChannelPage(props: Props) {
|
|||
<Tab>{editing ? __('Editing Your Channel') : __('About')}</Tab>
|
||||
<Tab disabled={editing}>{__('Comments')}</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<ChannelContent uri={uri} channelIsBlackListed={channelIsBlackListed} />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
{editing ? (
|
||||
<ChannelEdit
|
||||
uri={uri}
|
||||
doneEditing={doneEditing}
|
||||
updateThumb={v => setThumbPreview(v)}
|
||||
updateCover={v => setCoverPreview(v)}
|
||||
/>
|
||||
) : (
|
||||
<ChannelAbout uri={uri} />
|
||||
)}
|
||||
<ChannelAbout uri={uri} />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<ChannelDiscussion uri={uri} />
|
||||
|
|
7
ui/page/channelNew/index.js
Normal file
7
ui/page/channelNew/index.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { connect } from 'react-redux';
|
||||
import ChannelNew from './view';
|
||||
|
||||
const select = () => ({});
|
||||
const perform = () => ({});
|
||||
|
||||
export default connect(select, perform)(ChannelNew);
|
24
ui/page/channelNew/view.jsx
Normal file
24
ui/page/channelNew/view.jsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import ChannelEdit from 'component/channelEdit';
|
||||
import Page from 'component/page';
|
||||
import { withRouter } from 'react-router';
|
||||
|
||||
type Props = {
|
||||
history: { goBack: () => void },
|
||||
};
|
||||
|
||||
function ChannelNew(props: Props) {
|
||||
const { history } = props;
|
||||
return (
|
||||
<Page
|
||||
noSideNavigation
|
||||
backout={{ backFunction: () => history.goBack(), backTitle: __('Create Channel') }}
|
||||
className="main--auth-page"
|
||||
>
|
||||
<ChannelEdit onDone={history.goBack} />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
export default withRouter(ChannelNew);
|
|
@ -8,6 +8,7 @@ import Button from 'component/button';
|
|||
import YoutubeTransferStatus from 'component/youtubeTransferStatus';
|
||||
import Spinner from 'component/spinner';
|
||||
import Card from 'component/common/card';
|
||||
import * as PAGES from 'constants/pages';
|
||||
|
||||
type Props = {
|
||||
channels: Array<ChannelClaim>,
|
||||
|
@ -35,12 +36,14 @@ export default function ChannelsPage(props: Props) {
|
|||
<Card
|
||||
title={__('Your Channels')}
|
||||
titleActions={
|
||||
<Button
|
||||
button="secondary"
|
||||
icon={ICONS.CHANNEL}
|
||||
label={__('New Channel')}
|
||||
onClick={() => openModal(MODALS.CREATE_CHANNEL)}
|
||||
/>
|
||||
<>
|
||||
<Button
|
||||
button="secondary"
|
||||
icon={ICONS.CHANNEL}
|
||||
label={__('New Channel')}
|
||||
navigate={`/$/${PAGES.CHANNEL_NEW}`}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
isBodyList
|
||||
body={<ClaimList isCardBody loading={fetchingChannels} uris={channelUrls} />}
|
||||
|
|
|
@ -10,6 +10,11 @@ $metadata-z-index: 1;
|
|||
color: #fff;
|
||||
}
|
||||
|
||||
.channel-cover--edit {
|
||||
@extend .channel-cover;
|
||||
padding-top: 4rem;
|
||||
}
|
||||
|
||||
.channel-cover__custom {
|
||||
z-index: $cover-z-index;
|
||||
align-self: flex-start;
|
||||
|
@ -168,7 +173,6 @@ $metadata-z-index: 1;
|
|||
.channel__quick-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: var(--spacing-s);
|
||||
margin-left: var(--spacing-m);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -193,6 +197,67 @@ $metadata-z-index: 1;
|
|||
}
|
||||
}
|
||||
|
||||
.channel__edit-thumb {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: var(--spacing-l);
|
||||
margin-top: calc(var(--spacing-m) * 7);
|
||||
z-index: $metadata-z-index;
|
||||
flex-wrap: wrap;
|
||||
font-size: var(--font-base);
|
||||
|
||||
> * {
|
||||
padding: 0 var(--spacing-xs);
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: var(--spacing-m);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
> * {
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.channel__edit-cover {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: var(--spacing-m);
|
||||
margin-top: var(--spacing-m);
|
||||
z-index: $metadata-z-index;
|
||||
flex-wrap: wrap;
|
||||
font-size: var(--font-base);
|
||||
|
||||
> * {
|
||||
padding: 0 var(--spacing-xs);
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: var(--spacing-m);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
> * {
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.channel__uri-preview {
|
||||
position: absolute;
|
||||
top: -2rem;
|
||||
left: 0;
|
||||
z-index: $metadata-z-index;
|
||||
font-size: var(--font-base);
|
||||
color: var(--color-text-subtitle);
|
||||
}
|
||||
|
||||
.channel-name--inline {
|
||||
margin-left: var(--spacing-xs);
|
||||
}
|
||||
|
|
|
@ -43,6 +43,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.header__contents--between {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.header__navigation {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
|
|
|
@ -47,6 +47,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.tags__border {
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid var(--color-input-border);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.tag {
|
||||
@extend .badge;
|
||||
@extend .badge--tag;
|
||||
|
|
|
@ -27,6 +27,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
.tabs__list--select-asset {
|
||||
margin-bottom: var(--spacing-l);
|
||||
padding: var(--spacing-m) 0;
|
||||
|
||||
height: 4rem;
|
||||
border-bottom-left-radius: var(--card-radius);
|
||||
border-bottom-right-radius: var(--card-radius);
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
padding-left: var(--spacing-m);
|
||||
}
|
||||
}
|
||||
|
||||
.tab {
|
||||
@extend .button--link;
|
||||
margin-right: var(--spacing-l);
|
||||
|
|
Loading…
Reference in a new issue