This commit is contained in:
Sean Yesmunt 2019-09-27 14:56:15 -04:00
parent 4c014e3147
commit ecf5e52dd4
47 changed files with 462 additions and 453 deletions

View file

@ -24,6 +24,7 @@ module.name_mapper='^app\(.*\)$' -> '<PROJECT_ROOT>/src/ui/app\1'
module.name_mapper='^native\(.*\)$' -> '<PROJECT_ROOT>/src/ui/native\1'
module.name_mapper='^analytics\(.*\)$' -> '<PROJECT_ROOT>/src/ui/analytics\1'
module.name_mapper='^i18n\(.*\)$' -> '<PROJECT_ROOT>/src/ui/i18n\1'
module.name_mapper='^effects\(.*\)$' -> '<PROJECT_ROOT>/src/ui/effects\1'
module.name_mapper='^config\(.*\)$' -> '<PROJECT_ROOT>/config\1'

View file

@ -128,7 +128,7 @@
"husky": "^0.14.3",
"json-loader": "^0.5.4",
"lbry-format": "https://github.com/lbryio/lbry-format.git",
"lbry-redux": "lbryio/lbry-redux#d44cd9ca56dee784dba42c0cc13061ae75cbd46c",
"lbry-redux": "lbryio/lbry-redux#42bf926138872d14523be7191694309be4f37605",
"lbryinc": "lbryio/lbryinc#368040d64658cf2a4b8a7a6725ec1787329ce65d",
"lint-staged": "^7.0.2",
"localforage": "^1.7.1",

View file

@ -11,8 +11,7 @@ import useKonamiListener from 'util/enhanced-layout';
import Yrbl from 'component/yrbl';
import FileViewer from 'component/fileViewer';
import { withRouter } from 'react-router';
import usePrevious from 'util/use-previous';
import SyncBackgroundManager from 'component/syncBackgroundManager';
import usePrevious from 'effects/use-previous';
import Button from 'component/button';
export const MAIN_WRAPPER_CLASS = 'main-wrapper';
@ -121,7 +120,6 @@ function App(props: Props) {
<Router />
<ModalRouter />
<FileViewer pageUri={uri} />
<SyncBackgroundManager />
{/* @if TARGET='app' */}
{showUpgradeButton && (

View file

@ -3,7 +3,7 @@ import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
import React, { useRef } from 'react';
import Button from 'component/button';
import useHover from 'util/use-hover';
import useHover from 'effects/use-hover';
type Props = {
permanentUrl: ?string,

View file

@ -64,7 +64,7 @@ function ChannelContent(props: Props) {
</div>
)}
{!channelIsMine && <HiddenNsfwClaims className="card__subtitle" uri={uri} />}
{!channelIsMine && <HiddenNsfwClaims uri={uri} />}
{hasContent && !channelIsBlocked && !channelIsBlackListed && (
<ClaimList header={false} uris={claimsInChannel.map(claim => claim && claim.canonical_url)} />

View file

@ -182,7 +182,7 @@ function ChannelForm(props: Props) {
onChange={text => setParams({ ...params, description: text })}
/>
<TagSelect
title={false}
title={__('Add Tags')}
suggestMature
help={__('The better your tags are, the easier it will be for people to discover your channel.')}
empty={__('No tags added')}

View file

@ -6,7 +6,7 @@ import classnames from 'classnames';
import ClaimPreview from 'component/claimPreview';
import Spinner from 'component/spinner';
import { FormField } from 'component/common/form';
import usePersistedState from 'util/use-persisted-state';
import usePersistedState from 'effects/use-persisted-state';
const SORT_NEW = 'new';
const SORT_OLD = 'old';

View file

@ -187,9 +187,9 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
<div className="claim-preview-title">
{claim ? <TruncatedText text={title || claim.name} lines={1} /> : <span>{__('Nothing here')}</span>}
</div>
{actions !== undefined
? actions
: !hideActions && (
{!hideActions && actions !== undefined ? (
actions
) : (
<div className="card__actions--inline">
{isChannel && !channelIsBlocked && !claimIsMine && (
<SubscribeButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />

View file

@ -5,7 +5,7 @@ import { FormField, Form } from 'component/common/form';
import Button from 'component/button';
import ChannelSection from 'component/selectChannel';
import UnsupportedOnWeb from 'component/common/unsupported-on-web';
import usePersistedState from 'util/use-persisted-state';
import usePersistedState from 'effects/use-persisted-state';
type Props = {
uri: string,

View file

@ -5,7 +5,7 @@ import classnames from 'classnames';
import Icon from 'component/common/icon';
type Props = {
title: string | Node,
title?: string | Node,
subtitle?: string | Node,
body?: string | Node,
actions?: string | Node,
@ -16,6 +16,7 @@ export default function Card(props: Props) {
const { title, subtitle, body, actions, icon } = props;
return (
<section className={classnames('card')}>
{title && (
<div className="card__header">
<div className="section__flex">
{icon && <Icon sectionIcon icon={icon} />}
@ -25,6 +26,7 @@ export default function Card(props: Props) {
</div>
</div>
</div>
)}
{body && <div className={classnames('card__body', { 'card__body--with-icon': icon })}>{body}</div>}
{actions && (

View file

@ -6,8 +6,8 @@ import classnames from 'classnames';
import LoadingScreen from 'component/common/loading-screen';
import FileRender from 'component/fileRender';
import UriIndicator from 'component/uriIndicator';
import usePersistedState from 'util/use-persisted-state';
import usePrevious from 'util/use-previous';
import usePersistedState from 'effects/use-persisted-state';
import usePrevious from 'effects/use-previous';
import { FILE_WRAPPER_CLASS } from 'page/file/view';
import Draggable from 'react-draggable';
import Tooltip from 'component/common/tooltip';
@ -86,7 +86,7 @@ export default function FileViewer(props: Props) {
function handleResize() {
const element = document.querySelector(`.${FILE_WRAPPER_CLASS}`);
if (!element) {
console.error("Can't find file viewer wrapper to attach to the inline viewer to");
console.error("Can't find file viewer wrapper to attach to the inline viewer to"); // eslint-disable-line
return;
}

View file

@ -1,7 +1,6 @@
// @flow
import React from 'react';
import Button from 'component/button';
import classnames from 'classnames';
type Props = {
numberOfNsfwClaims: number,
@ -10,12 +9,12 @@ type Props = {
};
export default (props: Props) => {
const { numberOfNsfwClaims, obscureNsfw, className } = props;
const { numberOfNsfwClaims, obscureNsfw } = props;
return (
obscureNsfw &&
Boolean(numberOfNsfwClaims) && (
<div className={classnames('card--section', className || 'help')}>
<div className="section--padded section__subtitle">
{numberOfNsfwClaims} {numberOfNsfwClaims > 1 ? __('files') : __('file')} {__('hidden due to your')}{' '}
<Button button="link" navigate="/$/settings" label={__('content viewing preferences')} />.
</div>

View file

@ -1,10 +1,11 @@
// @flow
import React from 'react';
import classnames from 'classnames';
import usePersistedState from 'util/use-persisted-state';
import usePersistedState from 'effects/use-persisted-state';
import { FormField } from 'component/common/form';
import Button from 'component/button';
import LicenseType from './license-type';
import Card from 'component/common/card';
type Props = {
language: ?string,
@ -25,7 +26,9 @@ function PublishAdvanced(props: Props) {
}
return (
<section className="card card--section">
<Card
actions={
<React.Fragment>
{!hideSection && (
<div className={classnames({ 'card--disabled': !name })}>
<FormField
@ -83,9 +86,15 @@ function PublishAdvanced(props: Props) {
)}
<div className="card__actions">
<Button label={hideSection ? __('Additional Options') : __('Hide')} button="link" onClick={toggleHideSection} />
<Button
label={hideSection ? __('Additional Options') : __('Hide')}
button="link"
onClick={toggleHideSection}
/>
</div>
</section>
</React.Fragment>
}
/>
);
}

View file

@ -1,9 +1,10 @@
// @flow
import * as ICONS from 'constants/icons';
import React from 'react';
import { regexInvalidURI } from 'lbry-redux';
import classnames from 'classnames';
import FileSelector from 'component/common/file-selector';
import Button from 'component/button';
import Card from 'component/common/card';
type Props = {
name: ?string,
@ -11,10 +12,11 @@ type Props = {
isStillEditing: boolean,
balance: number,
updatePublishForm: ({}) => void,
disabled: boolean,
};
function PublishFile(props: Props) {
const { name, balance, filePath, isStillEditing, updatePublishForm } = props;
const { name, balance, filePath, isStillEditing, updatePublishForm, disabled } = props;
function handleFileChange(filePath: string, fileName: string) {
const publishFormParams: { filePath: string, name?: string } = { filePath };
@ -28,15 +30,14 @@ function PublishFile(props: Props) {
}
return (
<section
className={classnames('card card--section', {
'card--disabled': balance === 0,
})}
>
<h2 className="card__title">{isStillEditing ? __('Edit') : __('Publish')}</h2>
{isStillEditing && <p className="card__subtitle">{__('You are currently editing a claim.')}</p>}
<div className="card__content">
<Card
className={disabled ? 'card--disabled' : undefined}
icon={ICONS.PUBLISH}
disabled={balance === 0}
title={isStillEditing ? __('Edit') : __('Publish')}
subtitle={__('You are currently editing a claim.')}
actions={
<React.Fragment>
<FileSelector currentPath={filePath} onFileChosen={handleFileChange} />
{!isStillEditing && (
<p className="help">
@ -49,8 +50,9 @@ function PublishFile(props: Props) {
{__("If you don't choose a file, the file from your existing claim %name% will be used", { name: name })}
</p>
)}
</div>
</section>
</React.Fragment>
}
/>
);
}

View file

@ -14,6 +14,7 @@ import PublishName from 'component/publishName';
import PublishAdditionalOptions from 'component/publishAdditionalOptions';
import PublishFormErrors from 'component/publishFormErrors';
import SelectThumbnail from 'component/selectThumbnail';
import Card from 'component/common/card';
type Props = {
tags: Array<Tag>,
@ -127,13 +128,10 @@ function PublishForm(props: Props) {
<PublishFile />
<div className={classnames({ 'card--disabled': formDisabled })}>
<PublishText disabled={formDisabled} />
<div className="card card--section">
{/* This should probably be PublishThumbnail */}
<SelectThumbnail />
</div>
<div className="card card--section">
<Card actions={<SelectThumbnail />} />
<TagSelect
title={false}
title={__('Add Tags')}
suggestMature
help={__('The better your tags are, the easier it will be for people to discover your content.')}
empty={__('No tags added')}
@ -148,14 +146,18 @@ function PublishForm(props: Props) {
}}
tagsChosen={tags}
/>
</div>
<section className="card card--section">
<Card
actions={
<React.Fragment>
<ChannelSection channel={channel} onChannelChange={channel => updatePublishForm({ channel })} />
<p className="help">
{__('This is a username or handle that your content can be found under.')}{' '}
{__('Ex. @Marvel, @TheBeatles, @BooksByJoe')}
</p>
</section>
</React.Fragment>
}
/>
<PublishName disabled={formDisabled} />
<PublishPrice disabled={formDisabled} />

View file

@ -5,6 +5,7 @@ import { isNameValid } from 'lbry-redux';
import { FormField } from 'component/common/form';
import NameHelpText from './name-help-text';
import BidHelpText from './bid-help-text';
import Card from 'component/common/card';
type Props = {
name: string,
@ -73,7 +74,9 @@ function PublishName(props: Props) {
}, [bid, previousBidAmount, balance]);
return (
<section className="card card--section">
<Card
actions={
<React.Fragment>
<fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix">
<fieldset-section>
<label>{__('Name')}</label>
@ -111,10 +114,16 @@ function PublishName(props: Props) {
disabled={!name}
onChange={event => updatePublishForm({ bid: parseFloat(event.target.value) })}
helper={
<BidHelpText uri={uri} amountNeededForTakeover={amountNeededForTakeover} isResolvingUri={isResolvingUri} />
<BidHelpText
uri={uri}
amountNeededForTakeover={amountNeededForTakeover}
isResolvingUri={isResolvingUri}
/>
}
/>
</React.Fragment>
}
/>
</section>
);
}

View file

@ -1,6 +1,7 @@
// @flow
import React from 'react';
import { FormField, FormFieldPrice } from 'component/common/form';
import Card from 'component/common/card';
type Props = {
contentIsFree: boolean,
@ -13,7 +14,9 @@ function PublishPrice(props: Props) {
const { contentIsFree, fee, updatePublishForm, disabled } = props;
return (
<section className="card card--section">
<Card
actions={
<React.Fragment>
<FormField
type="radio"
name="content_free"
@ -46,7 +49,9 @@ function PublishPrice(props: Props) {
)}
</p>
)}
</section>
</React.Fragment>
}
/>
);
}

View file

@ -2,7 +2,8 @@
import React from 'react';
import { FormField } from 'component/common/form';
import Button from 'component/button';
import usePersistedState from 'util/use-persisted-state';
import usePersistedState from 'effects/use-persisted-state';
import Card from 'component/common/card';
type Props = {
title: ?string,
@ -19,7 +20,9 @@ function PublishText(props: Props) {
}
return (
<section className="card card--section">
<Card
actions={
<React.Fragment>
<FormField
type="text"
name="content_title"
@ -40,9 +43,15 @@ function PublishText(props: Props) {
onChange={value => updatePublishForm({ description: advancedEditor ? value : value.target.value })}
/>
<div className="card__actions">
<Button button="link" onClick={toggleMarkdown} label={advancedEditor ? 'Simple Editor' : 'Advanced Editor'} />
<Button
button="link"
onClick={toggleMarkdown}
label={advancedEditor ? 'Simple Editor' : 'Advanced Editor'}
/>
</div>
</section>
</React.Fragment>
}
/>
);
}

View file

@ -1,7 +1,7 @@
// @flow
import React from 'react';
import TotalBackground from './total-background.png';
import useTween from 'util/use-tween';
import useTween from 'effects/use-tween';
type Props = {
rewards: Array<Reward>,

View file

@ -69,7 +69,8 @@ function SideBar(props: Props) {
className="navigation-link"
activeClass="navigation-link--active"
/>
<ul className="navigation-links tags--vertical">
<section className="navigation-links__inline">
<ul className="navigation-links--small tags--vertical">
{followedTags.map(({ name }, key) => (
<li className="navigation-link__wrapper" key={name}>
<Tag navigate={`/$/tags?t${name}`} name={name} />
@ -88,6 +89,7 @@ function SideBar(props: Props) {
</li>
))}
</ul>
</section>
</nav>
</StickyBox>
);

View file

@ -124,11 +124,6 @@ export default class SplashScreen extends React.PureComponent<Props, State> {
clearTimeout(this.timeout);
}
//
//
// Try to unlock by default here
//
//
// Make sure there isn't another active modal (like INCOMPATIBLE_DAEMON)
if (launchedModal === false && !modal) {
this.setState({ launchedModal: true }, () => notifyUnlockWallet());
@ -153,10 +148,10 @@ export default class SplashScreen extends React.PureComponent<Props, State> {
});
}
} else if (wallet && wallet.blocks_behind > 0) {
const format = wallet.blocks_behind === 1 ? '%s block behind' : '%s blocks behind';
const amountBehind = wallet.blocks_behind === 1 ? '%amountBehind% block behind' : '%amountBehind% blocks behind';
this.setState({
message: __('Blockchain Sync'),
details: `${__('Catching up...')} (${__(format, wallet.blocks_behind)})`,
details: `${__('Catching up...')} (${__(amountBehind, { amountBehind: wallet.blocks_behind })})`,
});
} else if (
wallet &&

View file

@ -4,7 +4,7 @@ import * as ICONS from 'constants/icons';
import React, { useRef } from 'react';
import { parseURI } from 'lbry-redux';
import Button from 'component/button';
import useHover from 'util/use-hover';
import useHover from 'effects/use-hover';
type SubscriptionArgs = {
channelName: string,

View file

@ -1,24 +0,0 @@
import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux';
import { selectBalance } from 'lbry-redux';
import { doGetSync, selectSyncHash, selectUserVerifiedEmail } from 'lbryinc';
import { doSetClientSetting } from 'redux/actions/settings';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import WalletSecurityAndSync from './view';
const select = state => ({
balance: selectBalance(state),
verifiedEmail: selectUserVerifiedEmail(state),
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
hasSyncHash: selectSyncHash(state),
});
const perform = dispatch => ({
getSync: password => dispatch(doGetSync(password)),
setSync: value => dispatch(doSetClientSetting(SETTINGS.ENABLE_SYNC, value)),
});
export default connect(
select,
perform
)(WalletSecurityAndSync);

View file

@ -1,22 +0,0 @@
// @flow
import React from 'react';
type Props = {
syncEnabled: boolean,
verifiedEmail?: string,
getSync: () => void,
};
function SyncBackgroundManager(props: Props) {
const { syncEnabled, getSync, verifiedEmail } = props;
React.useEffect(() => {
if (syncEnabled && verifiedEmail) {
getSync();
}
}, [syncEnabled, verifiedEmail, getSync]);
return null;
}
export default SyncBackgroundManager;

View file

@ -4,7 +4,7 @@ import * as React from 'react';
import Button from 'component/button';
import Tag from 'component/tag';
import TagsSearch from 'component/tagsSearch';
import usePersistedState from 'util/use-persisted-state';
import usePersistedState from 'effects/use-persisted-state';
import analytics from 'analytics';
import Card from 'component/common/card';
@ -73,7 +73,15 @@ export default function TagSelect(props: Props) {
)}
</React.Fragment>
}
body={
subtitle={
help !== false && (
<span>
{help || __("The tags you follow will change what's trending for you.")}{' '}
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/trending" />.
</span>
)
}
actions={
<React.Fragment>
<TagsSearch
onRemove={handleTagClick}
@ -81,12 +89,6 @@ export default function TagSelect(props: Props) {
suggestMature={suggestMature && !hasMatureTag}
tagsPasssedIn={tagsToDisplay}
/>
{help !== false && (
<p className="help">
{help || __("The tags you follow will change what's trending for you.")}{' '}
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/trending" />.
</p>
)}
</React.Fragment>
}
/>

View file

@ -40,9 +40,7 @@ function UserEmail(props: Props) {
'This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to save account information and earn rewards.'
)}
actions={
!isVerified ? (
<Button button="primary" label={__('Add Email')} navigate={`/$/${PAGES.AUTH}`} />
) : (
isVerified ? (
<FormField
type="text"
className="form-field--copyable"
@ -60,6 +58,8 @@ function UserEmail(props: Props) {
inputButton={<UserSignOutButton button="inverse" />}
value={email || ''}
/>
) : (
<Button button="primary" label={__('Add Email')} navigate={`/$/${PAGES.AUTH}`} />
)
}
/>

View file

@ -30,6 +30,7 @@ function UserEmailNew(props: Props) {
}
React.useEffect(() => {
// Sync currently doesn't work for wallets with balances
if (syncEnabled && balance) {
setSync(false);
}
@ -56,7 +57,9 @@ function UserEmailNew(props: Props) {
name="sync_checkbox"
label={__('Sync your balance between devices')}
helper={
balance > 0 ? __('This is only available for empty wallets') : __('Maybe some more text about something')
balance > 0
? __("This is only available if you don't have a balance")
: __('Maybe some more text about something')
}
checked={syncEnabled}
onChange={() => setSync(!syncEnabled)}

View file

@ -33,9 +33,9 @@ const select = state => ({
youtubeChannels: selectYoutubeChannels(state),
userFetchPending: selectUserIsPending(state),
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
syncIsPending: selectGetSyncIsPending(state),
syncingWallet: selectGetSyncIsPending(state),
getSyncError: selectGetSyncErrorMessage(state),
syncHash: selectSyncHash(state),
hasSynced: Boolean(selectSyncHash(state)),
});
const perform = dispatch => ({

View file

@ -6,19 +6,11 @@ import UserEmailVerify from 'component/userEmailVerify';
import UserFirstChannel from 'component/userFirstChannel';
import { DEFAULT_BID_FOR_FIRST_CHANNEL } from 'component/userFirstChannel/view';
import { rewards as REWARDS } from 'lbryinc';
import usePrevious from 'util/use-previous';
import UserVerify from 'component/userVerify';
import Spinner from 'component/spinner';
import YoutubeTransferWelcome from 'component/youtubeTransferWelcome';
import SyncPassword from 'component/syncPassword';
/*
- Brand new user
- Brand new user, not auto approved
- Second device (first time user), first device has a password + rewards not approved
- Second device (first time user), first device has a password + rewards approved
*/
import useFetched from 'effects/use-fetched';
type Props = {
user: ?User,
@ -33,24 +25,12 @@ type Props = {
history: { replace: string => void },
location: { search: string },
youtubeChannels: Array<any>,
syncIsPending: boolean,
syncEnabled: boolean,
hasSynced: boolean,
syncingWallet: boolean,
getSyncError: ?string,
hasSyncedSuccessfully: boolean,
};
function useFetched(fetching) {
const wasFetching = usePrevious(fetching);
const [fetched, setFetched] = React.useState(false);
React.useEffect(() => {
if (wasFetching && !fetching) {
setFetched(true);
}
}, [wasFetching, fetching, setFetched]);
return fetched;
}
function UserSignIn(props: Props) {
const {
emailToVerify,
@ -65,9 +45,9 @@ function UserSignIn(props: Props) {
fetchUser,
youtubeChannels,
syncEnabled,
syncIsPending,
syncingWallet,
getSyncError,
syncHash,
hasSynced,
fetchingChannels,
} = props;
const { search } = location;
@ -76,52 +56,60 @@ function UserSignIn(props: Props) {
const hasVerifiedEmail = user && user.has_verified_email;
const rewardsApproved = user && user.is_reward_approved;
const hasFetchedReward = useFetched(claimingReward);
// const hasFetchedSync = useFetched(syncIsPending);
// const hasTriedSyncForReal = syncEnabled && hasFetchedSync;
const channelCount = channels ? channels.length : 0;
const hasClaimedEmailAward = claimedRewards.some(reward => reward.reward_type === REWARDS.TYPE_CONFIRM_EMAIL);
const hasYoutubeChannels = youtubeChannels && youtubeChannels.length;
const hasYoutubeChannels = youtubeChannels && Boolean(youtubeChannels.length);
const hasTransferrableYoutubeChannels = hasYoutubeChannels && youtubeChannels.some(channel => channel.transferable);
const hasPendingYoutubeTransfer =
hasYoutubeChannels && youtubeChannels.some(channel => channel.transfer_state === 'pending_transfer');
React.useEffect(() => {
if (
hasVerifiedEmail &&
balance !== undefined &&
!hasClaimedEmailAward &&
!hasFetchedReward &&
(!syncEnabled || (syncEnabled && syncHash))
) {
claimReward();
}
}, [hasVerifiedEmail, claimReward, balance, hasClaimedEmailAward, hasFetchedReward, syncEnabled, syncHash]);
// Complexity warning
// We can't just check if we are currently fetching something
// We may want to keep a component rendered while something is being fetched, instead of replacing it with the large spinner
// The verbose variable names are an attempt to alleviate _some_ of the confusion from handling all edge cases that come from
// reward claiming (plus the balance updating after), channel creation, account syncing, and youtube transfer
const canHijackSignInFlowWithSpinner = hasVerifiedEmail && balance === 0 && !getSyncError;
const isCurrentlyFetchingSomething = fetchingChannels || claimingReward || syncingWallet;
const isWaitingForSomethingToFinish =
// If the user has claimed the email award, we need to wait until the balance updates sometime in the future
!hasFetchedReward || (hasFetchedReward && hasClaimedEmailAward) || (syncEnabled && !hasSynced);
// The possible screens for the sign in flow
const showEmail = !emailToVerify && !hasVerifiedEmail;
const showEmailVerification = emailToVerify && !hasVerifiedEmail;
const showUserVerification = hasVerifiedEmail && !rewardsApproved;
const showSyncPassword = syncEnabled && getSyncError && !hasSynced;
const showChannelCreation =
hasVerifiedEmail && balance && balance > DEFAULT_BID_FOR_FIRST_CHANNEL && channelCount === 0 && !hasYoutubeChannels;
const showYoutubeTransfer =
hasVerifiedEmail && hasYoutubeChannels && (hasTransferrableYoutubeChannels || hasPendingYoutubeTransfer);
const showLoadingSpinner =
canHijackSignInFlowWithSpinner && (isCurrentlyFetchingSomething || isWaitingForSomethingToFinish);
React.useEffect(() => {
fetchUser();
}, [fetchUser]);
React.useEffect(() => {
// Don't claim the reward if sync is enabled until after a sync has been completed successfully
// If we do it before, we could end up trying to sync a wallet with a non-zero balance which will fail to sync
const delayForSync = syncEnabled && !hasSynced;
if (hasVerifiedEmail && !hasClaimedEmailAward && !hasFetchedReward && !delayForSync) {
claimReward();
}
}, [hasVerifiedEmail, claimReward, hasClaimedEmailAward, hasFetchedReward, syncEnabled, hasSynced]);
// Loop through this list from the end, until it finds a matching component
// If it never finds one, assume the user has completed every step and redirect them
const SIGN_IN_FLOW = [
!emailToVerify && !hasVerifiedEmail && <UserEmailNew />,
emailToVerify && !hasVerifiedEmail && <UserEmailVerify />,
hasVerifiedEmail && !rewardsApproved && <UserVerify />,
getSyncError && !syncHash && <SyncPassword />,
hasVerifiedEmail && balance > DEFAULT_BID_FOR_FIRST_CHANNEL && channelCount === 0 && !hasYoutubeChannels && (
<UserFirstChannel />
),
hasVerifiedEmail && hasYoutubeChannels && (hasTransferrableYoutubeChannels || hasPendingYoutubeTransfer) && (
<YoutubeTransferWelcome />
),
hasVerifiedEmail &&
balance === 0 &&
!getSyncError &&
(fetchingChannels ||
!hasFetchedReward ||
claimingReward ||
syncIsPending ||
(syncEnabled && !syncHash) ||
// Just claimed the email award, wait until the balance updates to move forward
(balance === 0 && hasFetchedReward && hasClaimedEmailAward)) && (
showEmail && <UserEmailNew />,
showEmailVerification && <UserEmailVerify />,
showUserVerification && <UserVerify />,
showSyncPassword && <SyncPassword />,
showChannelCreation && <UserFirstChannel />,
showYoutubeTransfer && <YoutubeTransferWelcome />,
showLoadingSpinner && (
<div className="main--empty">
<Spinner />
</div>

View file

@ -13,25 +13,33 @@ type Props = {
videosImported: ?Array<number>, // [currentAmountImported, totalAmountToImport]
};
const LBRY_YT_URL = 'https://lbry.com/youtube/status/';
const NOT_TRANSFERED = 'not_transferred';
const NOT_TRANSFERRED = 'not_transferred';
const PENDING_TRANSFER = 'pending_transfer';
const COMPLETED_TRANSFER = 'completed_transfer';
export default function YoutubeTransferStatus(props: Props) {
const { youtubeChannels, ytImportPending, claimChannels, videosImported, checkYoutubeTransfer, updateUser } = props;
const hasChannels = youtubeChannels && youtubeChannels.length;
const transferEnabled = youtubeChannels && youtubeChannels.some(el => el.transferable === true);
const transferComplete =
youtubeChannels &&
youtubeChannels.some(({ transfer_state: transferState }) => transferState === COMPLETED_TRANSFER);
let youtubeUrls =
youtubeChannels &&
youtubeChannels.map(
({ lbry_channel_name: channelName, channel_claim_id: claimId }) => `lbry://${channelName}#${claimId}`
);
let transferEnabled = false;
let transferStarted = false;
let transferComplete = false;
if (hasChannels) {
for (var i = 0; i < youtubeChannels.length; i++) {
const { transfer_state: transferState, transferable } = youtubeChannels[i];
if (transferable) {
transferEnabled = true;
}
if (transferState === COMPLETED_TRANSFER) {
transferComplete = true;
}
if (transferState === PENDING_TRANSFER) {
transferStarted = true;
}
}
}
let total;
let complete;
@ -44,7 +52,7 @@ export default function YoutubeTransferStatus(props: Props) {
const { transferable, transfer_state: transferState, sync_status: syncStatus } = channel;
if (!transferable) {
switch (transferState) {
case NOT_TRANSFERED:
case NOT_TRANSFERRED:
return syncStatus[0].toUpperCase() + syncStatus.slice(1);
case PENDING_TRANSFER:
return __('Transfer in progress');
@ -58,7 +66,7 @@ export default function YoutubeTransferStatus(props: Props) {
React.useEffect(() => {
// If a channel is transferrable, theres nothing to check
if (!transferComplete) {
if (transferStarted && !transferComplete) {
checkYoutubeTransfer();
let interval = setInterval(() => {
@ -70,30 +78,29 @@ export default function YoutubeTransferStatus(props: Props) {
clearInterval(interval);
};
}
}, [transferComplete, checkYoutubeTransfer, updateUser]);
}, [transferComplete, transferStarted, checkYoutubeTransfer, updateUser]);
return (
hasChannels &&
!transferComplete && (
<div>
<Card
title={youtubeUrls.length > 1 ? __('Your YouTube Channels') : __('Your YouTube Channel')}
title={youtubeChannels.length > 1 ? __('Your YouTube Channels') : __('Your YouTube Channel')}
subtitle={
<span>
{__('Your videos are currently being transferred. There is nothing else for you to do.')}{' '}
<Button button="link" href={LBRY_YT_URL} label={__('Learn more')} />.
{transferStarted
? __('Your videos are currently being transferred. There is nothing else for you to do.')
: __('Your videos are ready to be transferred.')}
</span>
}
body={
<section>
{youtubeUrls.map((url, index) => {
const channel = youtubeChannels[index];
{youtubeChannels.map((channel, index) => {
const { lbry_channel_name: channelName, channel_claim_id: claimId } = channel;
const url = `lbry://${channelName}#${claimId}`;
const transferState = getMessage(channel);
return (
<div
key={url}
style={{ border: '1px solid #ccc', borderRadius: 'var(--card-radius)', marginBottom: '1rem' }}
>
<div key={url} className="card--inline">
<ClaimPreview uri={url} actions={<span className="help">{transferState}</span>} properties={''} />
</div>
);

View file

@ -0,0 +1,17 @@
// @flow
import React from 'react';
import usePrevious from 'effects/use-previous';
// Returns true once a loading value has changed from false => true => false
export default function useFetched(fetching: boolean) {
const wasFetching = usePrevious(fetching);
const [fetched, setFetched] = React.useState(false);
React.useEffect(() => {
if (wasFetching && !fetching) {
setFetched(true);
}
}, [wasFetching, fetching, setFetched]);
return fetched;
}

View file

@ -3,7 +3,7 @@ import React from 'react';
import { Modal } from 'modal/modal';
import { Form, FormField } from 'component/common/form';
import Button from 'component/button';
import usePersistedState from 'util/use-persisted-state';
import usePersistedState from 'effects/use-persisted-state';
type Props = {
uri: string,

View file

@ -16,7 +16,7 @@ function DiscoverPage(props: Props) {
return (
<Page>
{email && <TagsSelect showClose title={__('Customize Your Homepage')} />}
{(email || !IS_WEB) && <TagsSelect showClose title={__('Customize Your Homepage')} />}
<ClaimListDiscover
hideCustomization={IS_WEB && !email}
personalView

View file

@ -38,9 +38,7 @@ function FollowingPage(props: Props) {
return (
<Page>
<div className="card card--section">
<TagsSelect showClose={false} title={__('Customize Your Tags')} />
</div>
<TagsSelect showClose={false} title={__('Follow New Tags')} />
<div className="card">
<ClaimList
header={viewingSuggestedSubs ? __('Discover New Channels') : __('Channels You Follow')}

View file

@ -3,7 +3,7 @@ import React, { useRef } from 'react';
import Page from 'component/page';
import ClaimListDiscover from 'component/claimListDiscover';
import Button from 'component/button';
import useHover from 'util/use-hover';
import useHover from 'effects/use-hover';
import analytics from 'analytics';
type Props = {

View file

@ -16,6 +16,7 @@ import {
makeSelectClaimIsMine,
doPopulateSharedUserState,
doFetchChannelListMine,
getSync,
} from 'lbry-redux';
import Native from 'native';
import { doFetchDaemonSettings } from 'redux/actions/settings';
@ -454,6 +455,8 @@ export function doSignIn() {
dispatch(doBalanceSubscribe());
dispatch(doCheckSubscriptionsInit());
dispatch(doFetchChannelListMine());
dispatch(getSync());
// @endif
Lbryio.call('user_settings', 'get').then(settings => {

View file

@ -55,15 +55,20 @@
margin-right: auto;
}
// "cards" inside cards
.card--inline {
box-shadow: none;
border-radius: none;
margin-bottom: 0;
border: 1px solid $lbry-gray-1;
border-radius: var(--card-radius);
[data-mode='dark'] & {
border-color: var(--dm-color-03);
}
}
.card--claim-preview-wrap {
@extend .card;
margin: var(--spacing-xlarge) 0;
min-width: 35rem;
}
.card--claim-preview-selected {
@ -241,7 +246,7 @@
.card__main-actions {
padding: var(--spacing-large);
background-color: rgba($lbry-blue-1, 0.1);
background-color: var(--color-card-actions);
color: darken($lbry-gray-5, 15%);
font-size: var(--font-body);

View file

@ -1,6 +1,12 @@
$border-color: rgba($lbry-teal-5, 0.1);
$border-color--dark: var(--dm-color-04);
.claim-list {
.claim-preview {
border-top: 1px solid $border-color;
}
}
.claim-list__header {
display: flex;
align-items: center;
@ -85,6 +91,7 @@ $border-color--dark: var(--dm-color-04);
}
.claim-preview {
flex: 1;
display: flex;
position: relative;
overflow: visible;
@ -99,14 +106,6 @@ $border-color--dark: var(--dm-color-04);
flex-shrink: 0;
margin-right: var(--spacing-medium);
}
}
.claim-preview {
border-top: 1px solid $border-color;
&:only-of-type {
border: none;
}
[data-mode='dark'] & {
color: $lbry-white;
@ -114,14 +113,6 @@ $border-color--dark: var(--dm-color-04);
}
}
.claim-preview--injected + .claim-preview {
border-top: 1px solid $border-color;
[data-mode='dark'] & {
border-color: $border-color--dark;
}
}
.claim-preview--large {
border: none;
padding: 0;

View file

@ -1,5 +1,5 @@
.icon__wrapper {
@extend .card__subtitle;
background-color: var(--color-card-actions);
display: flex;
align-items: center;
justify-content: center;

View file

@ -66,10 +66,13 @@
}
.main--contained {
max-width: 40rem;
min-width: 25rem;
margin: auto;
margin-top: 5rem;
display: flex;
flex-direction: column;
align-items: flex-start;
max-width: 40rem;
text-align: left;
}
.main--full-width {

View file

@ -39,6 +39,10 @@
font-size: var(--font-multiplier-small);
}
.navigation-links__inline {
margin-left: 1.7rem;
}
.navigation-link__wrapper {
margin: var(--spacing-miniscule) 0;
}

View file

@ -40,6 +40,7 @@ $large-breakpoint: 1921px;
// Color
--color-background: #f7f7f7;
--color-background--splash: #270f34;
--color-card-actions: #f7fbfe;
// Dark Mode
--dm-color-01: #ddd;

View file

@ -6850,9 +6850,9 @@ lazy-val@^1.0.3, lazy-val@^1.0.4:
yargs "^13.2.2"
zstd-codec "^0.1.1"
lbry-redux@lbryio/lbry-redux#d44cd9ca56dee784dba42c0cc13061ae75cbd46c:
lbry-redux@lbryio/lbry-redux#42bf926138872d14523be7191694309be4f37605:
version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/d44cd9ca56dee784dba42c0cc13061ae75cbd46c"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/42bf926138872d14523be7191694309be4f37605"
dependencies:
proxy-polyfill "0.1.6"
reselect "^3.0.0"