basic channel discovery in first run

This commit is contained in:
Sean Yesmunt 2020-02-26 13:39:03 -05:00
parent cf4dc25f34
commit 3e08d8e231
14 changed files with 109 additions and 24 deletions

View file

@ -989,4 +989,4 @@
"Share usage data with LBRY inc.": "Share usage data with LBRY inc.",
"Required": "Required",
"Email %help_link% or join our %chat_link% if you encounter any trouble verifying.": "Email %help_link% or join our %chat_link% if you encounter any trouble verifying."
}
}

View file

@ -70,14 +70,14 @@ export default function ClaimList(props: Props) {
useEffect(() => {
function handleScroll(e) {
if (page && pageSize && onScrollBottom && !scrollBottomCbMap[page]) {
const x = document.querySelector(`.${MAIN_WRAPPER_CLASS}`);
const mc = document.querySelector(`.${MAIN_CLASS}`);
const mainElWrapper = document.querySelector(`.${MAIN_WRAPPER_CLASS}`);
const main = document.querySelector(`.${MAIN_CLASS}`);
if (
x &&
mc &&
(window.scrollY + window.innerHeight >= x.offsetHeight ||
x.offsetHeight - mc.offsetHeight > PADDING_ALLOWANCE) &&
mainElWrapper &&
main &&
(window.scrollY + window.innerHeight >= mainElWrapper.offsetHeight ||
mainElWrapper.offsetHeight - main.offsetHeight > PADDING_ALLOWANCE) &&
!loading &&
urisLength >= pageSize
) {

View file

@ -50,9 +50,11 @@ type Props = {
header?: Node,
headerLabel?: string | Node,
name?: string,
pageSize?: number,
claimType?: string | Array<string>,
renderProperties?: Claim => Node,
includeSupportAction?: boolean,
noInfiniteScroll: boolean,
};
function ClaimListDiscover(props: Props) {
@ -76,8 +78,10 @@ function ClaimListDiscover(props: Props) {
header,
name,
claimType,
pageSize,
renderProperties,
includeSupportAction,
noInfiniteScroll,
} = props;
const didNavigateForward = history.action === 'PUSH';
const [page, setPage] = useState(1);
@ -100,7 +104,7 @@ function ClaimListDiscover(props: Props) {
name?: string,
claim_type?: string | Array<string>,
} = {
page_size: PAGE_SIZE,
page_size: pageSize || PAGE_SIZE,
page,
name,
// no_totals makes it so the sdk doesn't have to calculate total number pages for pagination
@ -226,7 +230,7 @@ function ClaimListDiscover(props: Props) {
}
function handleScrollBottom() {
if (!loading) {
if (!loading && !noInfiniteScroll) {
setPage(page + 1);
}
}

View file

@ -4,7 +4,7 @@ import classnames from 'classnames';
import Tag from 'component/tag';
const SLIM_TAGS = 1;
const NORMAL_TAGS = 4;
const NORMAL_TAGS = 3;
const LARGE_TAGS = 10;
type Props = {

View file

@ -1,6 +1,7 @@
// @flow
import type { Node } from 'react';
import * as ICONS from 'constants/icons';
import classnames from 'classnames';
import React from 'react';
import Button from 'component/button';
@ -8,19 +9,20 @@ type Props = {
message: string | Node,
actionText: string,
href?: string,
type?: string,
onClick?: () => void,
onClose?: () => void,
};
export default function Nag(props: Props) {
const { message, actionText, href, onClick, onClose } = props;
const { message, actionText, href, onClick, onClose, type } = props;
const buttonProps = onClick ? { onClick } : { href };
return (
<div className="nag">
<div className={classnames('nag', { 'nag--helpful': type === 'helpful' })}>
{message}
<Button className="nag__button" {...buttonProps}>
<Button className={classnames('nag__button', { 'nag__button--helpful': type === 'helpful' })} {...buttonProps}>
{actionText}
</Button>
{onClose && <Button className="nag__button nag__close" icon={ICONS.REMOVE} onClick={onClose} />}

View file

@ -139,7 +139,9 @@ function AppRouter(props: Props) {
{/* @if TARGET='app' */}
{welcomeVersion < WELCOME_VERSION && <Route path="/*" component={Welcome} />}
{/* @endif */}
<Redirect from={`/$/${PAGES.CHANNELS_FOLLOWING_MANAGE}`} to={`/$/${PAGES.CHANNELS_FOLLOWING_DISCOVER}`} />
<Route path={`/`} exact component={HomePage} />
<Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />
<Route path={`/$/${PAGES.AUTH}`} exact component={SignInPage} />

View file

@ -0,0 +1,11 @@
import { connect } from 'react-redux';
import { selectFollowedTags } from 'lbry-redux';
import { selectSubscriptions } from 'redux/selectors/subscriptions';
import UserChannelFollowIntro from './view';
const select = state => ({
followedTags: selectFollowedTags(state),
subscribedChannels: selectSubscriptions(state),
});
export default connect(select)(UserChannelFollowIntro);

View file

@ -0,0 +1,47 @@
// @flow
import React from 'react';
import ClaimListDiscover from 'component/claimListDiscover';
import { TYPE_TOP, TIME_ALL } from 'component/claimListDiscover/view';
import Nag from 'component/common/nag';
type Props = {
subscribedChannels: Array<Subscription>,
onContinue: () => void,
};
function UserChannelFollowIntro(props: Props) {
const { subscribedChannels, onContinue } = props;
const followingCount = (subscribedChannels && subscribedChannels.length) || 0;
return (
<React.Fragment>
<h1 className="section__title--large">{__('Find Channels to Follow')}</h1>
<p className="section__subtitle">
{__(
'LBRY works better if you find and follow at least 5 creators you like. You can also block channels you never want to see.'
)}
</p>
<ClaimListDiscover
defaultTypeSort={TYPE_TOP}
defaultTimeSort={TIME_ALL}
pageSize={99}
claimType="channel"
noInfiniteScroll
/>
{followingCount > 0 && (
<Nag
type="helpful"
message={
followingCount === 1
? __('Nice! You are currently following %followingCount% creator', { followingCount })
: __('Nice! You are currently following %followingCount% creators', { followingCount })
}
actionText={__('Continue')}
onClick={onContinue}
/>
)}
</React.Fragment>
);
}
export default UserChannelFollowIntro;

View file

@ -4,6 +4,7 @@ import { withRouter } from 'react-router';
import UserEmailNew from 'component/userEmailNew';
import UserEmailVerify from 'component/userEmailVerify';
import UserFirstChannel from 'component/userFirstChannel';
import UserChannelFollowIntro from 'component/userChannelFollowIntro';
import { DEFAULT_BID_FOR_FIRST_CHANNEL } from 'component/userFirstChannel/view';
import { rewards as REWARDS, YOUTUBE_STATUSES } from 'lbryinc';
import UserVerify from 'component/userVerify';
@ -11,6 +12,7 @@ import Spinner from 'component/spinner';
import YoutubeTransferStatus from 'component/youtubeTransferStatus';
import SyncPassword from 'component/syncPassword';
import useFetched from 'effects/use-fetched';
import usePersistedState from 'effects/use-persisted-state';
import Confetti from 'react-confetti';
type Props = {
@ -58,6 +60,7 @@ function UserSignIn(props: Props) {
const redirect = urlParams.get('redirect');
const shouldRedirectImmediately = urlParams.get('immediate');
const [initialSignInStep, setInitialSignInStep] = React.useState();
const [hasSeenFollowList, setHasSeenFollowList] = usePersistedState('channel-follow-intro', false);
const hasVerifiedEmail = user && user.has_verified_email;
const rewardsApproved = user && user.is_reward_approved;
const hasFetchedReward = useFetched(claimingReward);
@ -73,11 +76,6 @@ function UserSignIn(props: Props) {
// 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, channel creation, account syncing, and youtube transfer
const canHijackSignInFlowWithSpinner = hasVerifiedEmail && !getSyncError;
const isCurrentlyFetchingSomething = fetchingChannels || claimingReward || syncingWallet || creatingChannel;
const isWaitingForSomethingToFinish =
// If the user has claimed the email award, we need to wait until the balance updates sometime in the future
(!hasFetchedReward && !hasClaimedEmailAward) || (syncEnabled && !hasSynced);
// The possible screens for the sign in flow
const showEmail = !emailToVerify && !hasVerifiedEmail;
const showEmailVerification = emailToVerify && !hasVerifiedEmail;
@ -91,6 +89,12 @@ function UserSignIn(props: Props) {
channelCount === 0 &&
!hasYoutubeChannels;
const showYoutubeTransfer = hasVerifiedEmail && hasYoutubeChannels && !isYoutubeTransferComplete;
const showFollowIntro = hasVerifiedEmail && !hasSeenFollowList && channelCount > 0;
const canHijackSignInFlowWithSpinner = hasVerifiedEmail && !getSyncError && !showFollowIntro;
const isCurrentlyFetchingSomething = fetchingChannels || claimingReward || syncingWallet || creatingChannel;
const isWaitingForSomethingToFinish =
// If the user has claimed the email award, we need to wait until the balance updates sometime in the future
(!hasFetchedReward && !hasClaimedEmailAward) || (syncEnabled && !hasSynced);
const showLoadingSpinner =
canHijackSignInFlowWithSpinner && (isCurrentlyFetchingSomething || isWaitingForSomethingToFinish);
@ -115,6 +119,7 @@ function UserSignIn(props: Props) {
showEmailVerification && <UserEmailVerify />,
showUserVerification && <UserVerify skipLink={redirect} />,
showChannelCreation && <UserFirstChannel />,
showFollowIntro && <UserChannelFollowIntro onContinue={() => setHasSeenFollowList(true)} />,
showYoutubeTransfer && (
<div>
<YoutubeTransferStatus /> <Confetti recycle={false} style={{ position: 'fixed' }} />

View file

@ -24,7 +24,7 @@ exports.TAGS_FOLLOWING = 'following/tags';
exports.TAGS_FOLLOWING_MANAGE = 'following/tags/manage';
exports.CHANNELS_FOLLOWING = 'following/channels';
exports.CHANNELS_FOLLOWING_MANAGE = 'following/channels/manage';
exports.CHANNELS_FOLLOWING_DISCOVER = 'following/channels/discover';
exports.CHANNELS_FOLLOWING_DISCOVER = 'following/discover';
exports.WALLET = 'wallet';
exports.BLOCKED = 'blocked';
exports.CHANNELS = 'channels';

View file

@ -31,7 +31,7 @@ function ChannelsFollowingDiscover(props: Props) {
rowData.push({
title: 'Top Channels Of All Time',
options: {
pageSize: 8,
pageSize: 12,
claimType: 'channel',
orderBy: ['effective_amount'],
},
@ -50,7 +50,7 @@ function ChannelsFollowingDiscover(props: Props) {
rowData.push({
title: 'Trending Channels',
options: {
pageSize: 4,
pageSize: 8,
claimType: 'channel',
orderBy: ['trending_group', 'trending_mixed'],
},

View file

@ -1,6 +1,5 @@
.wunderbar {
flex: 1;
height: 100%;
cursor: text;
display: flex;
align-items: center;

View file

@ -1,6 +1,7 @@
.nag {
z-index: 9999;
position: fixed;
left: 0;
bottom: 0;
width: 100%;
padding: var(--spacing-small);
@ -16,6 +17,11 @@
}
}
.nag--helpful {
background-color: var(--color-secondary);
color: var(--color-white);
}
.nag__button {
line-height: 1;
margin-left: var(--spacing-small);
@ -27,7 +33,17 @@
&:hover {
background-color: var(--color-white);
color: var(--color-nag);
color: var(--color-white);
}
}
.nag__button--helpful {
// color: var(--color-primary);
// border-color: var(--color-primary);
&:hover {
background-color: var(--color-secondary-alt);
color: var(--color-secondary);
}
}

View file

@ -49,7 +49,6 @@
// Search
--color-search-suggestion: #212529;
--color-search-suggestion-background: #cce6fb;
--color-wunderbar: var(--color-primary);
// Snack
--color-snack-bg: var(--color-primary);