basic repost UI
This commit is contained in:
parent
799b4843d6
commit
776de49c15
15 changed files with 279 additions and 43 deletions
|
@ -130,7 +130,7 @@
|
|||
"imagesloaded": "^4.1.4",
|
||||
"json-loader": "^0.5.4",
|
||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||
"lbry-redux": "lbryio/lbry-redux#87ae7faf1c1d5ffa86feb578899596f6ea2a5fd9",
|
||||
"lbry-redux": "lbryio/lbry-redux#ea215ac31c82338ce60963224380bf4e9ecc1702",
|
||||
"lbryinc": "lbryio/lbryinc#6a59102c52673502569d2c43bd4ee58c315fb2e4",
|
||||
"lint-staged": "^7.0.2",
|
||||
"localforage": "^1.7.1",
|
||||
|
|
|
@ -930,4 +930,4 @@
|
|||
"Playing in %seconds_left% seconds": "Playing in %seconds_left% seconds",
|
||||
"Up Next by %channel%": "Up Next by %channel%",
|
||||
"Install Now": "Install Now"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,6 +96,7 @@ function App(props: Props) {
|
|||
const urlParams = new URLSearchParams(search);
|
||||
const rawReferrerParam = urlParams.get('r');
|
||||
const sanitizedReferrerParam = rawReferrerParam && rawReferrerParam.replace(':', '#');
|
||||
const wrapperElement = appRef.current;
|
||||
|
||||
let uri;
|
||||
try {
|
||||
|
@ -122,7 +123,9 @@ function App(props: Props) {
|
|||
}, [sanitizedReferrerParam, isRewardApproved, referredRewardAvailable]);
|
||||
|
||||
useEffect(() => {
|
||||
ReactModal.setAppElement(appRef.current);
|
||||
if (wrapperElement) {
|
||||
ReactModal.setAppElement(wrapperElement);
|
||||
}
|
||||
fetchAccessToken();
|
||||
|
||||
// @if TARGET='app'
|
||||
|
@ -130,7 +133,7 @@ function App(props: Props) {
|
|||
fetchTransactions(1, TX_LIST.LATEST_PAGE_SIZE);
|
||||
fetchChannelListMine(); // This needs to be done for web too...
|
||||
// @endif
|
||||
}, [fetchRewards, fetchTransactions, fetchAccessToken, fetchChannelListMine]);
|
||||
}, [fetchRewards, fetchTransactions, fetchAccessToken, fetchChannelListMine, wrapperElement]);
|
||||
|
||||
useEffect(() => {
|
||||
// $FlowFixMe
|
||||
|
|
12
ui/component/common/error-text.jsx
Normal file
12
ui/component/common/error-text.jsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
children: any,
|
||||
};
|
||||
|
||||
export default function ErrorText(props: Props) {
|
||||
const { children } = props;
|
||||
|
||||
return <span className="error-text">{children}</span>;
|
||||
}
|
|
@ -78,7 +78,6 @@ export class FormField extends React.PureComponent<Props> {
|
|||
...inputProps
|
||||
} = this.props;
|
||||
const errorMessage = typeof error === 'object' ? error.message : error;
|
||||
|
||||
const Wrapper = blockWrap
|
||||
? ({ children: innerChildren }) => <fieldset-section class="radio">{innerChildren}</fieldset-section>
|
||||
: ({ children: innerChildren }) => <span className="radio">{innerChildren}</span>;
|
||||
|
@ -160,7 +159,7 @@ export class FormField extends React.PureComponent<Props> {
|
|||
input = (
|
||||
<React.Fragment>
|
||||
<fieldset-section>
|
||||
{label && (
|
||||
{(label || errorMessage) && (
|
||||
<label htmlFor={name}>
|
||||
{errorMessage ? <span className="error-text">{errorMessage}</span> : label}
|
||||
</label>
|
||||
|
|
|
@ -51,6 +51,12 @@ function FileActions(props: Props) {
|
|||
label={__('Share')}
|
||||
onClick={() => openModal(MODALS.SOCIAL_SHARE, { uri, webShareable })}
|
||||
/>
|
||||
<Button
|
||||
button="alt"
|
||||
icon={ICONS.REPOST}
|
||||
label={__('Repost')}
|
||||
onClick={() => openModal(MODALS.REPOST, { uri })}
|
||||
/>
|
||||
|
||||
{!claimIsMine && (
|
||||
<Button
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
// @flow
|
||||
import { CHANNEL_NEW, CHANNEL_ANONYMOUS } from 'constants/claim';
|
||||
import React, { Fragment } from 'react';
|
||||
import { FormField } from 'component/common/form';
|
||||
import BusyIndicator from 'component/common/busy-indicator';
|
||||
import ChannelCreate from 'component/channelCreate';
|
||||
|
||||
import { CHANNEL_NEW, CHANNEL_ANONYMOUS } from 'constants/claim';
|
||||
|
||||
type Props = {
|
||||
channel: string, // currently selected channel
|
||||
channels: ?Array<ChannelClaim>,
|
||||
|
@ -78,36 +76,30 @@ class ChannelSection extends React.PureComponent<Props, State> {
|
|||
|
||||
return (
|
||||
<Fragment>
|
||||
{fetchingChannels ? (
|
||||
<BusyIndicator message="Updating channels" />
|
||||
) : (
|
||||
<Fragment>
|
||||
<FormField
|
||||
name="channel"
|
||||
label={label || __('Channel')}
|
||||
type="select"
|
||||
onChange={this.handleChannelChange}
|
||||
value={channel}
|
||||
>
|
||||
{!hideAnon && <option value={CHANNEL_ANONYMOUS}>{__('Anonymous')}</option>}
|
||||
{channels &&
|
||||
channels.map(({ name, claim_id: claimId }) => (
|
||||
<option key={claimId} value={name}>
|
||||
{name}
|
||||
</option>
|
||||
))}
|
||||
{injected &&
|
||||
injected.map(item => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
<option value={CHANNEL_NEW}>{__('New channel...')}</option>}
|
||||
</FormField>
|
||||
<FormField
|
||||
name="channel"
|
||||
label={label || __('Channel')}
|
||||
type="select"
|
||||
onChange={this.handleChannelChange}
|
||||
value={channel}
|
||||
>
|
||||
{!hideAnon && <option value={CHANNEL_ANONYMOUS}>{__('Anonymous')}</option>}
|
||||
{channels &&
|
||||
channels.map(({ name, claim_id: claimId }) => (
|
||||
<option key={claimId} value={name}>
|
||||
{name}
|
||||
</option>
|
||||
))}
|
||||
{injected &&
|
||||
injected.map(item => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
{!fetchingChannels && <option value={CHANNEL_NEW}>{__('New channel...')}</option>}
|
||||
</FormField>
|
||||
|
||||
{addingChannel && <ChannelCreate onSuccess={this.handleChangeToNewChannel} />}
|
||||
</Fragment>
|
||||
)}
|
||||
{addingChannel && <ChannelCreate onSuccess={this.handleChangeToNewChannel} />}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ function TransactionListTable(props: Props) {
|
|||
function isRevokeable(txid: string, nout: number) {
|
||||
const outpoint = `${txid}:${nout}`;
|
||||
const { mySupports, myClaims } = props;
|
||||
|
||||
return !!mySupports[outpoint] || myClaims.has(outpoint);
|
||||
}
|
||||
|
||||
|
|
|
@ -35,3 +35,4 @@ export const CREATE_CHANNEL = 'create_channel';
|
|||
export const YOUTUBE_WELCOME = 'youtube_welcome';
|
||||
export const MOBILE_NAVIGATION = 'mobile_navigation';
|
||||
export const SET_REFERRER = 'set_referrer';
|
||||
export const REPOST = 'repost';
|
||||
|
|
33
ui/modal/modalRepost/index.js
Normal file
33
ui/modal/modalRepost/index.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doHideModal } from 'redux/actions/app';
|
||||
import {
|
||||
makeSelectClaimForUri,
|
||||
makeSelectTitleForUri,
|
||||
selectBalance,
|
||||
selectMyChannelClaims,
|
||||
doRepost,
|
||||
selectRepostError,
|
||||
selectRepostLoading,
|
||||
doClearRepostError,
|
||||
doToast,
|
||||
} from 'lbry-redux';
|
||||
import ModalRepost from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
channels: selectMyChannelClaims(state),
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
title: makeSelectTitleForUri(props.uri)(state),
|
||||
balance: selectBalance(state),
|
||||
error: selectRepostError(state),
|
||||
reposting: selectRepostLoading(state),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
{
|
||||
doHideModal,
|
||||
doRepost,
|
||||
doClearRepostError,
|
||||
doToast,
|
||||
}
|
||||
)(ModalRepost);
|
187
ui/modal/modalRepost/view.jsx
Normal file
187
ui/modal/modalRepost/view.jsx
Normal file
|
@ -0,0 +1,187 @@
|
|||
// @flow
|
||||
import { CHANNEL_NEW, MINIMUM_PUBLISH_BID, INVALID_NAME_ERROR } from 'constants/claim';
|
||||
import React from 'react';
|
||||
import { Modal } from 'modal/modal';
|
||||
import Card from 'component/common/card';
|
||||
import Button from 'component/button';
|
||||
import SelectChannel from 'component/selectChannel';
|
||||
import ErrorText from 'component/common/error-text';
|
||||
import { FormField } from 'component/common/form';
|
||||
import { parseURI, isNameValid, creditsToString } from 'lbry-redux';
|
||||
|
||||
type Props = {
|
||||
doHideModal: () => void,
|
||||
doToast: ({ message: string }) => void,
|
||||
doClearRepostError: () => void,
|
||||
doRepost: StreamRepostOptions => Promise<*>,
|
||||
title: string,
|
||||
claim: ?StreamClaim,
|
||||
balance: number,
|
||||
channels: ?Array<ChannelClaim>,
|
||||
error: ?string,
|
||||
reposting: boolean,
|
||||
};
|
||||
|
||||
function ModalRepost(props: Props) {
|
||||
const {
|
||||
doHideModal,
|
||||
doToast,
|
||||
doClearRepostError,
|
||||
doRepost,
|
||||
title,
|
||||
claim,
|
||||
balance,
|
||||
channels,
|
||||
error,
|
||||
reposting,
|
||||
} = props;
|
||||
const defaultName = claim && `${claim.name}-repost`;
|
||||
const originalClaimId = claim && claim.claim_id;
|
||||
|
||||
const [repostChannel, setRepostChannel] = React.useState<?{ claimId: string, name: string }>();
|
||||
const [showAdvanced, setShowAdvanced] = React.useState();
|
||||
const [repostBid, setRepostBid] = React.useState(0.1);
|
||||
const [repostName, setRepostName] = React.useState(defaultName);
|
||||
const [repostNameError, setRepostNameError] = React.useState();
|
||||
const [repostBidError, setRepostBidError] = React.useState();
|
||||
|
||||
const channelStrings = channels && channels.map(channel => channel.permanent_url).join(',');
|
||||
React.useEffect(() => {
|
||||
if (!repostChannel && channelStrings) {
|
||||
const channels = channelStrings.split(',');
|
||||
const newChannelUrl = channels[0];
|
||||
const { claimName, claimId } = parseURI(newChannelUrl);
|
||||
setRepostChannel({ name: claimName, claimId });
|
||||
}
|
||||
}, [channelStrings]);
|
||||
|
||||
React.useEffect(() => {
|
||||
let bidError;
|
||||
if (repostBid === 0) {
|
||||
bidError = __('Deposit cannot be 0');
|
||||
} else if (balance === repostBid) {
|
||||
bidError = __('Please decrease your deposit to account for transaction fees');
|
||||
} else if (balance < repostBid) {
|
||||
bidError = __('Deposit cannot be higher than your balance');
|
||||
} else if (repostBid < MINIMUM_PUBLISH_BID) {
|
||||
bidError = __('Your deposit must be higher');
|
||||
}
|
||||
|
||||
setRepostBidError(bidError);
|
||||
}, [repostBid, balance]);
|
||||
|
||||
React.useEffect(() => {
|
||||
let nameError;
|
||||
if (!repostName) {
|
||||
nameError = __('A name is required');
|
||||
} else if (!isNameValid(repostName, false)) {
|
||||
nameError = INVALID_NAME_ERROR;
|
||||
}
|
||||
|
||||
setRepostNameError(nameError);
|
||||
}, [repostName]);
|
||||
|
||||
function handleSubmit() {
|
||||
if (repostName && repostBid && repostChannel && originalClaimId) {
|
||||
doRepost({
|
||||
name: repostName,
|
||||
bid: creditsToString(repostBid),
|
||||
channel_id: repostChannel.claimId,
|
||||
claim_id: originalClaimId,
|
||||
}).then(() => {
|
||||
doHideModal();
|
||||
doToast({ message: __('Woohoo! Sucessfully reposted this claim.') });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleCloseModal() {
|
||||
doClearRepostError();
|
||||
doHideModal();
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen type="card" onAborted={handleCloseModal} onConfirmed={handleCloseModal}>
|
||||
<Card
|
||||
title={
|
||||
<span>
|
||||
Repost <em>{title}</em>
|
||||
</span>
|
||||
}
|
||||
subtitle={
|
||||
error && <ErrorText>{__('There was an error reposting this claim. Please try again later.')}</ErrorText>
|
||||
}
|
||||
body={
|
||||
<div>
|
||||
<SelectChannel
|
||||
label="Channel to repost on"
|
||||
hideAnon
|
||||
channel={repostChannel ? repostChannel.name : undefined}
|
||||
onChannelChange={newChannel => setRepostChannel(newChannel)}
|
||||
/>
|
||||
<div className="section__actions">
|
||||
{!showAdvanced && (
|
||||
<Button
|
||||
button="link"
|
||||
label={showAdvanced ? 'Hide' : __('Advanced')}
|
||||
onClick={() => setShowAdvanced(!showAdvanced)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{showAdvanced && (
|
||||
<React.Fragment>
|
||||
<fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix">
|
||||
<fieldset-section>
|
||||
<label>{__('Name')}</label>
|
||||
<div className="form-field__prefix">{`lbry://${
|
||||
!repostChannel || repostChannel.name === CHANNEL_NEW ? '' : `${repostChannel.name}/`
|
||||
}`}</div>
|
||||
</fieldset-section>
|
||||
<FormField
|
||||
type="text"
|
||||
name="repost_name"
|
||||
value={repostName}
|
||||
error={repostNameError}
|
||||
onChange={event => setRepostName(event.target.value)}
|
||||
/>
|
||||
</fieldset-group>
|
||||
<div className="form-field__help">
|
||||
{__('The name of your repost, something about reposting to help search')}
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
type="number"
|
||||
name="repost_bid"
|
||||
min="0"
|
||||
step="any"
|
||||
placeholder="0.123"
|
||||
className="form-field--price-amount"
|
||||
label={__('Deposit (LBC)')}
|
||||
postfix="LBC"
|
||||
value={repostBid}
|
||||
error={repostBidError}
|
||||
disabled={!repostName}
|
||||
onChange={event => setRepostBid(parseFloat(event.target.value))}
|
||||
onWheel={e => e.stopPropagation()}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
actions={
|
||||
<React.Fragment>
|
||||
<Button
|
||||
disabled={reposting || repostBidError || repostNameError}
|
||||
button="primary"
|
||||
label={reposting ? __('Reposting') : __('Repost')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<Button button="link" label={__('Cancel')} onClick={handleCloseModal} />
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModalRepost;
|
|
@ -34,6 +34,7 @@ import ModalYoutubeWelcome from 'modal/modalYoutubeWelcome';
|
|||
import ModalCreateChannel from 'modal/modalChannelCreate';
|
||||
import ModalMobileNavigation from 'modal/modalMobileNavigation';
|
||||
import ModalSetReferrer from 'modal/modalSetReferrer';
|
||||
import ModalRepost from 'modal/modalRepost';
|
||||
|
||||
type Props = {
|
||||
modal: { id: string, modalProps: {} },
|
||||
|
@ -125,6 +126,8 @@ function ModalRouter(props: Props) {
|
|||
return <ModalMobileNavigation {...modalProps} />;
|
||||
case MODALS.SET_REFERRER:
|
||||
return <ModalSetReferrer {...modalProps} />;
|
||||
case MODALS.REPOST:
|
||||
return <ModalRepost {...modalProps} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
@extend .button--link;
|
||||
color: var(--color-text-subtitle);
|
||||
max-width: 100%;
|
||||
height: 1.2em;
|
||||
vertical-align: text-top;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
transition: color 0.2s;
|
||||
|
|
|
@ -77,8 +77,9 @@ fieldset-group {
|
|||
border-bottom-left-radius: var(--border-radius);
|
||||
border-right: 0;
|
||||
border-color: var(--color-input-border);
|
||||
color: var(--color-input-placeholder);
|
||||
color: var(--color-gray-5);
|
||||
background-color: var(--color-input-bg);
|
||||
border-right: 1px solid var(--color-gray-1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,7 +91,7 @@ fieldset-group {
|
|||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-color: var(--color-input-border);
|
||||
padding-left: 0;
|
||||
padding-left: var(--spacing-xsmall);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ $breakpoint-medium: 1150px;
|
|||
--card-box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
|
||||
// Modal
|
||||
--modal-width: 440px;
|
||||
--modal-width: 550px;
|
||||
|
||||
// Animation :)
|
||||
--animation-duration: 0.2s;
|
||||
|
|
Loading…
Add table
Reference in a new issue