From 4815aa9ff13e5776f29002b65f44e5ec65c0753f Mon Sep 17 00:00:00 2001 From: Sean Yesmunt <sean@lbry.io> Date: Mon, 12 Oct 2020 17:12:46 -0400 Subject: [PATCH] add reward rate + analytics link on channels page --- ui/component/claimList/view.jsx | 3 ++ ui/component/claimPreview/view.jsx | 9 ++++-- ui/component/creatorAnalytics/view.jsx | 2 +- ui/page/channels/view.jsx | 45 ++++++++++++++++++++++++++ ui/page/creatorDashboard/view.jsx | 23 +++++++++++++ ui/scss/component/_claim-list.scss | 4 +++ ui/scss/init/_gui.scss | 4 +++ 7 files changed, 87 insertions(+), 3 deletions(-) diff --git a/ui/component/claimList/view.jsx b/ui/component/claimList/view.jsx index 823b3ae77..685e33fa7 100644 --- a/ui/component/claimList/view.jsx +++ b/ui/component/claimList/view.jsx @@ -36,6 +36,7 @@ type Props = { injectedItem: ?Node, timedOutMessage?: Node, tileLayout?: boolean, + renderActions?: Claim => ?Node, }; export default function ClaimList(props: Props) { @@ -59,6 +60,7 @@ export default function ClaimList(props: Props) { injectedItem, timedOutMessage, tileLayout = false, + renderActions, } = props; const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW); @@ -147,6 +149,7 @@ export default function ClaimList(props: Props) { includeSupportAction={includeSupportAction} showUnresolvedClaim={showUnresolvedClaims} properties={renderProperties || (type !== 'small' ? undefined : false)} + renderActions={renderActions} showUserBlocked={showHiddenByUser} hideBlock={hideBlock} customShouldHide={(claim: StreamClaim) => { diff --git a/ui/component/claimPreview/view.jsx b/ui/component/claimPreview/view.jsx index d1a1f462e..b4a2bc3f0 100644 --- a/ui/component/claimPreview/view.jsx +++ b/ui/component/claimPreview/view.jsx @@ -59,6 +59,8 @@ type Props = { customShouldHide?: Claim => boolean, showUnresolvedClaim?: boolean, includeSupportAction?: boolean, + hideActions?: boolean, + renderActions?: Claim => ?Node, }; const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => { @@ -91,12 +93,14 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => { customShouldHide, showUnresolvedClaim, includeSupportAction, + hideActions = false, + renderActions, } = props; const shouldFetch = claim === undefined || (claim !== null && claim.value_type === 'channel' && isEmpty(claim.meta) && !pending); const abandoned = !isResolvingUri && !claim; const showPublishLink = abandoned && !showUnresolvedClaim && placeholder === 'publish'; - const hideActions = type === 'small' || type === 'tooltip'; + const shouldHideActions = hideActions || type === 'small' || type === 'tooltip'; const canonicalUrl = claim && claim.canonical_url; let isValid = false; if (uri) { @@ -286,7 +290,8 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => { <div className="claim-preview__actions"> {!pending && ( <> - {hideActions ? null : actions !== undefined ? ( + {renderActions && claim && renderActions(claim)} + {shouldHideActions || renderActions ? null : actions !== undefined ? ( actions ) : ( <div className="claim-preview__primary-actions"> diff --git a/ui/component/creatorAnalytics/view.jsx b/ui/component/creatorAnalytics/view.jsx index faaef2e45..b508bb750 100644 --- a/ui/component/creatorAnalytics/view.jsx +++ b/ui/component/creatorAnalytics/view.jsx @@ -26,7 +26,7 @@ export default function CreatorAnalytics(props: Props) { const history = useHistory(); const [stats, setStats] = React.useState(); const [error, setError] = React.useState(); - const [fetchingStats, setFetchingStats] = React.useState(false); + const [fetchingStats, setFetchingStats] = React.useState(true); const claimId = claim && claim.claim_id; const channelHasClaims = claim && claim.meta && claim.meta.claims_in_channel && claim.meta.claims_in_channel > 0; diff --git a/ui/page/channels/view.jsx b/ui/page/channels/view.jsx index 1ed8566d4..d968f5b5a 100644 --- a/ui/page/channels/view.jsx +++ b/ui/page/channels/view.jsx @@ -1,12 +1,14 @@ // @flow import * as ICONS from 'constants/icons'; import React, { useEffect } from 'react'; +import { Lbryio } from 'lbryinc'; import ClaimList from 'component/claimList'; import Page from 'component/page'; import Button from 'component/button'; import YoutubeTransferStatus from 'component/youtubeTransferStatus'; import Spinner from 'component/spinner'; import Yrbl from 'component/yrbl'; +import LbcSymbol from 'component/common/lbc-symbol'; import * as PAGES from 'constants/pages'; type Props = { @@ -19,6 +21,7 @@ type Props = { export default function ChannelsPage(props: Props) { const { channels, channelUrls, fetchChannelListMine, fetchingChannels, youtubeChannels } = props; + const [rewardData, setRewardData] = React.useState(); const hasYoutubeChannels = youtubeChannels && Boolean(youtubeChannels.length); const hasPendingChannels = channels && channels.some(channel => channel.confirmations < 0); @@ -26,6 +29,10 @@ export default function ChannelsPage(props: Props) { fetchChannelListMine(); }, [fetchChannelListMine, hasPendingChannels]); + useEffect(() => { + Lbryio.call('user_rewards', 'view_rate').then(data => setRewardData(data)); + }, [setRewardData]); + return ( <Page> <div className="card-stack"> @@ -44,6 +51,44 @@ export default function ChannelsPage(props: Props) { } loading={fetchingChannels} uris={channelUrls} + renderActions={claim => { + const claimsInChannel = claim.meta.claims_in_channel; + return claimsInChannel === 0 ? ( + <span /> + ) : ( + <div className="section__actions"> + <Button + button="alt" + icon={ICONS.ANALYTICS} + label={__('Analytics')} + navigate={`/$/${PAGES.CREATOR_DASHBOARD}?channel=${encodeURIComponent(claim.canonical_url)}`} + /> + </div> + ); + }} + renderProperties={claim => { + const claimsInChannel = claim.meta.claims_in_channel; + if (!claim || claimsInChannel === 0) { + return null; + } + + const channelRewardData = + rewardData && + rewardData.rates.find(data => { + return data.channel_claim_id === claim.claim_id; + }); + + if (channelRewardData) { + return ( + <span className="claim-preview__custom-properties"> + <span className="help--inline">{__('Earnings per view')}</span> + <LbcSymbol postfix={channelRewardData.view_rate.toFixed(2)} /> + </span> + ); + } else { + return null; + } + }} /> )} </div> diff --git a/ui/page/creatorDashboard/view.jsx b/ui/page/creatorDashboard/view.jsx index 5aab9382a..b67c5f9aa 100644 --- a/ui/page/creatorDashboard/view.jsx +++ b/ui/page/creatorDashboard/view.jsx @@ -8,14 +8,23 @@ import CreatorAnalytics from 'component/creatorAnalytics'; import ChannelSelector from 'component/channelSelector'; import usePersistedState from 'effects/use-persisted-state'; import Yrbl from 'component/yrbl'; +import { useHistory } from 'react-router'; type Props = { channels: Array<ChannelClaim>, fetchingChannels: boolean, }; +const SELECTED_CHANNEL_QUERY_PARAM = 'channel'; + export default function CreatorDashboardPage(props: Props) { const { channels, fetchingChannels } = props; + const { + push, + location: { search, pathname }, + } = useHistory(); + const urlParams = new URLSearchParams(search); + const channelFromUrl = urlParams.get(SELECTED_CHANNEL_QUERY_PARAM); const [selectedChannelUrl, setSelectedChannelUrl] = usePersistedState('analytics-selected-channel'); const hasChannels = channels && channels.length > 0; const firstChannel = hasChannels && channels[0]; @@ -33,6 +42,19 @@ export default function CreatorDashboardPage(props: Props) { } }, [setSelectedChannelUrl, selectedChannelUrl, firstChannelUrl, channelFoundForSelectedChannelUrl]); + React.useEffect(() => { + if (channelFromUrl) { + const decodedChannel = decodeURIComponent(channelFromUrl); + setSelectedChannelUrl(decodedChannel); + } + }, [channelFromUrl, setSelectedChannelUrl]); + + function updateUrl(channelUrl) { + const newUrlParams = new URLSearchParams(); + newUrlParams.append(SELECTED_CHANNEL_QUERY_PARAM, encodeURIComponent(channelUrl)); + push(`${pathname}?${newUrlParams.toString()}`); + } + return ( <Page> {fetchingChannels && ( @@ -59,6 +81,7 @@ export default function CreatorDashboardPage(props: Props) { <ChannelSelector selectedChannelUrl={selectedChannelUrl} onChannelSelect={newChannelUrl => { + updateUrl(newChannelUrl); setSelectedChannelUrl(newChannelUrl); }} /> diff --git a/ui/scss/component/_claim-list.scss b/ui/scss/component/_claim-list.scss index d9c5c11fd..95884fdb3 100644 --- a/ui/scss/component/_claim-list.scss +++ b/ui/scss/component/_claim-list.scss @@ -200,6 +200,10 @@ color: var(--color-text); } +.claim-preview__custom-properties { + text-align: right; +} + .claim-preview-metadata { display: flex; flex-direction: column; diff --git a/ui/scss/init/_gui.scss b/ui/scss/init/_gui.scss index 79bdc5908..c8f740fbc 100644 --- a/ui/scss/init/_gui.scss +++ b/ui/scss/init/_gui.scss @@ -230,6 +230,10 @@ textarea { @extend .help; margin-top: 0; margin-bottom: 0; + + &:not(:last-child) { + margin-bottom: 0; + } } .help--card-actions {