basic channel discovery in first run
This commit is contained in:
parent
cf4dc25f34
commit
3e08d8e231
14 changed files with 109 additions and 24 deletions
|
@ -70,14 +70,14 @@ export default function ClaimList(props: Props) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleScroll(e) {
|
function handleScroll(e) {
|
||||||
if (page && pageSize && onScrollBottom && !scrollBottomCbMap[page]) {
|
if (page && pageSize && onScrollBottom && !scrollBottomCbMap[page]) {
|
||||||
const x = document.querySelector(`.${MAIN_WRAPPER_CLASS}`);
|
const mainElWrapper = document.querySelector(`.${MAIN_WRAPPER_CLASS}`);
|
||||||
const mc = document.querySelector(`.${MAIN_CLASS}`);
|
const main = document.querySelector(`.${MAIN_CLASS}`);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
x &&
|
mainElWrapper &&
|
||||||
mc &&
|
main &&
|
||||||
(window.scrollY + window.innerHeight >= x.offsetHeight ||
|
(window.scrollY + window.innerHeight >= mainElWrapper.offsetHeight ||
|
||||||
x.offsetHeight - mc.offsetHeight > PADDING_ALLOWANCE) &&
|
mainElWrapper.offsetHeight - main.offsetHeight > PADDING_ALLOWANCE) &&
|
||||||
!loading &&
|
!loading &&
|
||||||
urisLength >= pageSize
|
urisLength >= pageSize
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -50,9 +50,11 @@ type Props = {
|
||||||
header?: Node,
|
header?: Node,
|
||||||
headerLabel?: string | Node,
|
headerLabel?: string | Node,
|
||||||
name?: string,
|
name?: string,
|
||||||
|
pageSize?: number,
|
||||||
claimType?: string | Array<string>,
|
claimType?: string | Array<string>,
|
||||||
renderProperties?: Claim => Node,
|
renderProperties?: Claim => Node,
|
||||||
includeSupportAction?: boolean,
|
includeSupportAction?: boolean,
|
||||||
|
noInfiniteScroll: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function ClaimListDiscover(props: Props) {
|
function ClaimListDiscover(props: Props) {
|
||||||
|
@ -76,8 +78,10 @@ function ClaimListDiscover(props: Props) {
|
||||||
header,
|
header,
|
||||||
name,
|
name,
|
||||||
claimType,
|
claimType,
|
||||||
|
pageSize,
|
||||||
renderProperties,
|
renderProperties,
|
||||||
includeSupportAction,
|
includeSupportAction,
|
||||||
|
noInfiniteScroll,
|
||||||
} = props;
|
} = props;
|
||||||
const didNavigateForward = history.action === 'PUSH';
|
const didNavigateForward = history.action === 'PUSH';
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
|
@ -100,7 +104,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
name?: string,
|
name?: string,
|
||||||
claim_type?: string | Array<string>,
|
claim_type?: string | Array<string>,
|
||||||
} = {
|
} = {
|
||||||
page_size: PAGE_SIZE,
|
page_size: pageSize || PAGE_SIZE,
|
||||||
page,
|
page,
|
||||||
name,
|
name,
|
||||||
// no_totals makes it so the sdk doesn't have to calculate total number pages for pagination
|
// 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() {
|
function handleScrollBottom() {
|
||||||
if (!loading) {
|
if (!loading && !noInfiniteScroll) {
|
||||||
setPage(page + 1);
|
setPage(page + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import classnames from 'classnames';
|
||||||
import Tag from 'component/tag';
|
import Tag from 'component/tag';
|
||||||
|
|
||||||
const SLIM_TAGS = 1;
|
const SLIM_TAGS = 1;
|
||||||
const NORMAL_TAGS = 4;
|
const NORMAL_TAGS = 3;
|
||||||
const LARGE_TAGS = 10;
|
const LARGE_TAGS = 10;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import type { Node } from 'react';
|
import type { Node } from 'react';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
|
import classnames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
|
||||||
|
@ -8,19 +9,20 @@ type Props = {
|
||||||
message: string | Node,
|
message: string | Node,
|
||||||
actionText: string,
|
actionText: string,
|
||||||
href?: string,
|
href?: string,
|
||||||
|
type?: string,
|
||||||
onClick?: () => void,
|
onClick?: () => void,
|
||||||
onClose?: () => void,
|
onClose?: () => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Nag(props: Props) {
|
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 };
|
const buttonProps = onClick ? { onClick } : { href };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="nag">
|
<div className={classnames('nag', { 'nag--helpful': type === 'helpful' })}>
|
||||||
{message}
|
{message}
|
||||||
<Button className="nag__button" {...buttonProps}>
|
<Button className={classnames('nag__button', { 'nag__button--helpful': type === 'helpful' })} {...buttonProps}>
|
||||||
{actionText}
|
{actionText}
|
||||||
</Button>
|
</Button>
|
||||||
{onClose && <Button className="nag__button nag__close" icon={ICONS.REMOVE} onClick={onClose} />}
|
{onClose && <Button className="nag__button nag__close" icon={ICONS.REMOVE} onClick={onClose} />}
|
||||||
|
|
|
@ -139,7 +139,9 @@ function AppRouter(props: Props) {
|
||||||
{/* @if TARGET='app' */}
|
{/* @if TARGET='app' */}
|
||||||
{welcomeVersion < WELCOME_VERSION && <Route path="/*" component={Welcome} />}
|
{welcomeVersion < WELCOME_VERSION && <Route path="/*" component={Welcome} />}
|
||||||
{/* @endif */}
|
{/* @endif */}
|
||||||
|
|
||||||
<Redirect from={`/$/${PAGES.CHANNELS_FOLLOWING_MANAGE}`} to={`/$/${PAGES.CHANNELS_FOLLOWING_DISCOVER}`} />
|
<Redirect from={`/$/${PAGES.CHANNELS_FOLLOWING_MANAGE}`} to={`/$/${PAGES.CHANNELS_FOLLOWING_DISCOVER}`} />
|
||||||
|
|
||||||
<Route path={`/`} exact component={HomePage} />
|
<Route path={`/`} exact component={HomePage} />
|
||||||
<Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />
|
<Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />
|
||||||
<Route path={`/$/${PAGES.AUTH}`} exact component={SignInPage} />
|
<Route path={`/$/${PAGES.AUTH}`} exact component={SignInPage} />
|
||||||
|
|
11
ui/component/userChannelFollowIntro/index.js
Normal file
11
ui/component/userChannelFollowIntro/index.js
Normal 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);
|
47
ui/component/userChannelFollowIntro/view.jsx
Normal file
47
ui/component/userChannelFollowIntro/view.jsx
Normal 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;
|
|
@ -4,6 +4,7 @@ import { withRouter } from 'react-router';
|
||||||
import UserEmailNew from 'component/userEmailNew';
|
import UserEmailNew from 'component/userEmailNew';
|
||||||
import UserEmailVerify from 'component/userEmailVerify';
|
import UserEmailVerify from 'component/userEmailVerify';
|
||||||
import UserFirstChannel from 'component/userFirstChannel';
|
import UserFirstChannel from 'component/userFirstChannel';
|
||||||
|
import UserChannelFollowIntro from 'component/userChannelFollowIntro';
|
||||||
import { DEFAULT_BID_FOR_FIRST_CHANNEL } from 'component/userFirstChannel/view';
|
import { DEFAULT_BID_FOR_FIRST_CHANNEL } from 'component/userFirstChannel/view';
|
||||||
import { rewards as REWARDS, YOUTUBE_STATUSES } from 'lbryinc';
|
import { rewards as REWARDS, YOUTUBE_STATUSES } from 'lbryinc';
|
||||||
import UserVerify from 'component/userVerify';
|
import UserVerify from 'component/userVerify';
|
||||||
|
@ -11,6 +12,7 @@ import Spinner from 'component/spinner';
|
||||||
import YoutubeTransferStatus from 'component/youtubeTransferStatus';
|
import YoutubeTransferStatus from 'component/youtubeTransferStatus';
|
||||||
import SyncPassword from 'component/syncPassword';
|
import SyncPassword from 'component/syncPassword';
|
||||||
import useFetched from 'effects/use-fetched';
|
import useFetched from 'effects/use-fetched';
|
||||||
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
import Confetti from 'react-confetti';
|
import Confetti from 'react-confetti';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -58,6 +60,7 @@ function UserSignIn(props: Props) {
|
||||||
const redirect = urlParams.get('redirect');
|
const redirect = urlParams.get('redirect');
|
||||||
const shouldRedirectImmediately = urlParams.get('immediate');
|
const shouldRedirectImmediately = urlParams.get('immediate');
|
||||||
const [initialSignInStep, setInitialSignInStep] = React.useState();
|
const [initialSignInStep, setInitialSignInStep] = React.useState();
|
||||||
|
const [hasSeenFollowList, setHasSeenFollowList] = usePersistedState('channel-follow-intro', false);
|
||||||
const hasVerifiedEmail = user && user.has_verified_email;
|
const hasVerifiedEmail = user && user.has_verified_email;
|
||||||
const rewardsApproved = user && user.is_reward_approved;
|
const rewardsApproved = user && user.is_reward_approved;
|
||||||
const hasFetchedReward = useFetched(claimingReward);
|
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
|
// 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
|
// 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
|
// 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
|
// The possible screens for the sign in flow
|
||||||
const showEmail = !emailToVerify && !hasVerifiedEmail;
|
const showEmail = !emailToVerify && !hasVerifiedEmail;
|
||||||
const showEmailVerification = emailToVerify && !hasVerifiedEmail;
|
const showEmailVerification = emailToVerify && !hasVerifiedEmail;
|
||||||
|
@ -91,6 +89,12 @@ function UserSignIn(props: Props) {
|
||||||
channelCount === 0 &&
|
channelCount === 0 &&
|
||||||
!hasYoutubeChannels;
|
!hasYoutubeChannels;
|
||||||
const showYoutubeTransfer = hasVerifiedEmail && hasYoutubeChannels && !isYoutubeTransferComplete;
|
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 =
|
const showLoadingSpinner =
|
||||||
canHijackSignInFlowWithSpinner && (isCurrentlyFetchingSomething || isWaitingForSomethingToFinish);
|
canHijackSignInFlowWithSpinner && (isCurrentlyFetchingSomething || isWaitingForSomethingToFinish);
|
||||||
|
|
||||||
|
@ -115,6 +119,7 @@ function UserSignIn(props: Props) {
|
||||||
showEmailVerification && <UserEmailVerify />,
|
showEmailVerification && <UserEmailVerify />,
|
||||||
showUserVerification && <UserVerify skipLink={redirect} />,
|
showUserVerification && <UserVerify skipLink={redirect} />,
|
||||||
showChannelCreation && <UserFirstChannel />,
|
showChannelCreation && <UserFirstChannel />,
|
||||||
|
showFollowIntro && <UserChannelFollowIntro onContinue={() => setHasSeenFollowList(true)} />,
|
||||||
showYoutubeTransfer && (
|
showYoutubeTransfer && (
|
||||||
<div>
|
<div>
|
||||||
<YoutubeTransferStatus /> <Confetti recycle={false} style={{ position: 'fixed' }} />
|
<YoutubeTransferStatus /> <Confetti recycle={false} style={{ position: 'fixed' }} />
|
||||||
|
|
|
@ -24,7 +24,7 @@ exports.TAGS_FOLLOWING = 'following/tags';
|
||||||
exports.TAGS_FOLLOWING_MANAGE = 'following/tags/manage';
|
exports.TAGS_FOLLOWING_MANAGE = 'following/tags/manage';
|
||||||
exports.CHANNELS_FOLLOWING = 'following/channels';
|
exports.CHANNELS_FOLLOWING = 'following/channels';
|
||||||
exports.CHANNELS_FOLLOWING_MANAGE = 'following/channels/manage';
|
exports.CHANNELS_FOLLOWING_MANAGE = 'following/channels/manage';
|
||||||
exports.CHANNELS_FOLLOWING_DISCOVER = 'following/channels/discover';
|
exports.CHANNELS_FOLLOWING_DISCOVER = 'following/discover';
|
||||||
exports.WALLET = 'wallet';
|
exports.WALLET = 'wallet';
|
||||||
exports.BLOCKED = 'blocked';
|
exports.BLOCKED = 'blocked';
|
||||||
exports.CHANNELS = 'channels';
|
exports.CHANNELS = 'channels';
|
||||||
|
|
|
@ -31,7 +31,7 @@ function ChannelsFollowingDiscover(props: Props) {
|
||||||
rowData.push({
|
rowData.push({
|
||||||
title: 'Top Channels Of All Time',
|
title: 'Top Channels Of All Time',
|
||||||
options: {
|
options: {
|
||||||
pageSize: 8,
|
pageSize: 12,
|
||||||
claimType: 'channel',
|
claimType: 'channel',
|
||||||
orderBy: ['effective_amount'],
|
orderBy: ['effective_amount'],
|
||||||
},
|
},
|
||||||
|
@ -50,7 +50,7 @@ function ChannelsFollowingDiscover(props: Props) {
|
||||||
rowData.push({
|
rowData.push({
|
||||||
title: 'Trending Channels',
|
title: 'Trending Channels',
|
||||||
options: {
|
options: {
|
||||||
pageSize: 4,
|
pageSize: 8,
|
||||||
claimType: 'channel',
|
claimType: 'channel',
|
||||||
orderBy: ['trending_group', 'trending_mixed'],
|
orderBy: ['trending_group', 'trending_mixed'],
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
.wunderbar {
|
.wunderbar {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 100%;
|
|
||||||
cursor: text;
|
cursor: text;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
.nag {
|
.nag {
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: var(--spacing-small);
|
padding: var(--spacing-small);
|
||||||
|
@ -16,6 +17,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nag--helpful {
|
||||||
|
background-color: var(--color-secondary);
|
||||||
|
color: var(--color-white);
|
||||||
|
}
|
||||||
|
|
||||||
.nag__button {
|
.nag__button {
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
margin-left: var(--spacing-small);
|
margin-left: var(--spacing-small);
|
||||||
|
@ -27,7 +33,17 @@
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-white);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,6 @@
|
||||||
// Search
|
// Search
|
||||||
--color-search-suggestion: #212529;
|
--color-search-suggestion: #212529;
|
||||||
--color-search-suggestion-background: #cce6fb;
|
--color-search-suggestion-background: #cce6fb;
|
||||||
--color-wunderbar: var(--color-primary);
|
|
||||||
|
|
||||||
// Snack
|
// Snack
|
||||||
--color-snack-bg: var(--color-primary);
|
--color-snack-bg: var(--color-primary);
|
||||||
|
|
Loading…
Add table
Reference in a new issue