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 {