creator analytics improvements
This commit is contained in:
parent
a391f35542
commit
837158218e
6 changed files with 205 additions and 72 deletions
|
@ -17,7 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
### Fixed
|
||||
|
||||
- Fixed the title of the subscribe button getting out of sync on fast hover movements _community pr!_ ([#4054](https://github.com/lbryio/lbry-desktop/pull/4054))
|
||||
|
||||
- Handle errors better on the creator analytics page and add a new card for stats on users most recent content ([#4043](https://github.com/lbryio/lbry-desktop/pull/4043))
|
||||
- Add fallback when images fail to load _community pr!_ ([#4019](https://github.com/lbryio/lbry-desktop/pull/4019))
|
||||
- Fetch new content when clicking LBRY logo while on homepage _community pr!_ ([#4031](https://github.com/lbryio/lbry-desktop/pull/4031))
|
||||
- Aligns text across browsers and desktop _community pr!_ ([#4050](https://github.com/lbryio/lbry-desktop/pull/4050))
|
||||
|
|
|
@ -1166,5 +1166,30 @@
|
|||
"Enter Your lbry.tv Password": "Enter Your lbry.tv Password",
|
||||
"Signing in as %email%": "Signing in as %email%",
|
||||
"Forgot Password?": "Forgot Password?",
|
||||
"Use Magic Link": "Use Magic Link"
|
||||
"Use Magic Link": "Use Magic Link",
|
||||
"No recent publishes": "No recent publishes",
|
||||
"Error Fetching Stats": "Error Fetching Stats",
|
||||
"Something something something. Make sure you are signed in with the correct email and have data sharing on.": "Something something something. Make sure you are signed in with the correct email and have data sharing on.",
|
||||
"%follower_count_weekly_change% this week": "%follower_count_weekly_change% this week",
|
||||
"%all_content_views% views": "%all_content_views% views",
|
||||
"+ %all_content_views_weekly_change% this week": "+ %all_content_views_weekly_change% this week",
|
||||
"%lbc_received_changed% this week": "%lbc_received_changed% this week",
|
||||
" Earnings may also include any LBC you've sent yourself or added as support. We are working on making this more accurate. Check your wallet page for the correct total balance.": " Earnings may also include any LBC you've sent yourself or added as support. We are working on making this more accurate. Check your wallet page for the correct total balance.",
|
||||
"Your Recent Content": "Your Recent Content",
|
||||
"No recent publishes found for this channel. Publish something new and track how it's performing here.": "No recent publishes found for this channel. Publish something new and track how it's performing here.",
|
||||
"Most Viewed Content": "Most Viewed Content",
|
||||
"%all_time_top_views% views - %all_time_views_weekly_change% this week": "%all_time_top_views% views - %all_time_views_weekly_change% this week",
|
||||
"Successfully abandoned your support.": "Successfully abandoned your support.",
|
||||
"/wallet": "/wallet",
|
||||
"Send a tip to %url%": "Send a tip to %url%",
|
||||
"You sent %amount% LBC as a tip, Mahalo!": "You sent %amount% LBC as a tip, Mahalo!",
|
||||
"No Stats Found": "No Stats Found",
|
||||
"Sorry about that. Try refreshing or something else.": "Sorry about that. Try refreshing or something else.",
|
||||
"You are not able to see those stats right now. Make sure you are signed in with the correct email and have data sharing turned on.": "You are not able to see those stats right now. Make sure you are signed in with the correct email and have data sharing turned on.",
|
||||
"You are not able to see this channel's stats right now. Make sure you are signed in with the correct email and have data sharing turned on.": "You are not able to see this channel's stats right now. Make sure you are signed in with the correct email and have data sharing turned on.",
|
||||
"You are not able to see this channel's stats. Make sure you are signed in with the correct email and have data sharing turned on.": "You are not able to see this channel's stats. Make sure you are signed in with the correct email and have data sharing turned on.",
|
||||
"There are no stats for this channel. Make sure you are signed in with the correct email and have data sharing turned on.": "There are no stats for this channel. Make sure you are signed in with the correct email and have data sharing turned on.",
|
||||
"%follower_count% followers": "%follower_count% followers",
|
||||
"%lbc_received% LBC Earned": "%lbc_received% LBC Earned",
|
||||
"Earnings may also include any LBC you've sent yourself or added as support. We are working on making this more accurate. Check your wallet page for the correct total balance.": "Earnings may also include any LBC you've sent yourself or added as support. We are working on making this more accurate. Check your wallet page for the correct total balance."
|
||||
}
|
|
@ -1,10 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectMyChannelClaims, selectFetchingMyChannels, doPrepareEdit } from 'lbry-redux';
|
||||
import { makeSelectClaimForUri, doPrepareEdit } from 'lbry-redux';
|
||||
import CreatorAnalytics from './view';
|
||||
|
||||
const select = state => ({
|
||||
channels: selectMyChannelClaims(state),
|
||||
fetchingChannels: selectFetchingMyChannels(state),
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
|
|
|
@ -3,107 +3,116 @@ import * as ICONS from 'constants/icons';
|
|||
import * as PAGES from 'constants/pages';
|
||||
import React from 'react';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import ChannelSelector from 'component/channelSelector';
|
||||
import ClaimPreview from 'component/claimPreview';
|
||||
import Card from 'component/common/card';
|
||||
import Spinner from 'component/spinner';
|
||||
import Icon from 'component/common/icon';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import Button from 'component/button';
|
||||
import Yrbl from 'component/yrbl';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import analytics from 'analytics';
|
||||
|
||||
type Props = {
|
||||
channels: Array<ChannelClaim>,
|
||||
claim: ?ChannelClaim,
|
||||
fetchingChannels: boolean,
|
||||
prepareEdit: string => void,
|
||||
};
|
||||
|
||||
const UNAUTHENTICATED_ERROR = 'unauthenticated';
|
||||
const GENERIC_ERROR = 'error';
|
||||
|
||||
export default function CreatorAnalytics(props: Props) {
|
||||
const { channels, prepareEdit } = props;
|
||||
const { prepareEdit, claim } = props;
|
||||
const history = useHistory();
|
||||
const [stats, setStats] = React.useState();
|
||||
const [selectedChannelUrl, setSelectedChannelUrl] = usePersistedState('analytics-selected-channel');
|
||||
const [error, setError] = React.useState();
|
||||
const [fetchingStats, setFetchingStats] = React.useState(false);
|
||||
const hasChannels = channels && channels.length > 0;
|
||||
const firstChannel = hasChannels && channels[0];
|
||||
const firstChannelUrl = firstChannel && (firstChannel.canonical_url || firstChannel.permanent_url); // permanent_url is needed for pending publishes
|
||||
const selectedChannelClaim =
|
||||
channels &&
|
||||
channels.find(claim => claim.canonical_url === selectedChannelUrl || claim.permanent_url === selectedChannelUrl);
|
||||
const selectedChannelClaimId = selectedChannelClaim && selectedChannelClaim.claim_id;
|
||||
const channelFoundForSelectedChannelUrl =
|
||||
channels &&
|
||||
channels.find(channel => {
|
||||
return selectedChannelUrl === channel.canonical_url || selectedChannelUrl === channel.permanent_url;
|
||||
});
|
||||
const claimId = claim && claim.claim_id;
|
||||
const channelHasClaims = claim && claim.meta && claim.meta.claims_in_channel && claim.meta.claims_in_channel > 0;
|
||||
|
||||
React.useEffect(() => {
|
||||
// set default channel
|
||||
if ((!selectedChannelUrl || !channelFoundForSelectedChannelUrl) && firstChannelUrl) {
|
||||
setSelectedChannelUrl(firstChannelUrl);
|
||||
}
|
||||
}, [selectedChannelUrl, firstChannelUrl, channelFoundForSelectedChannelUrl]);
|
||||
setStats(null);
|
||||
}, [claimId]);
|
||||
|
||||
const channelForEffect = JSON.stringify(selectedChannelClaim);
|
||||
const channelForEffect = JSON.stringify(claim);
|
||||
React.useEffect(() => {
|
||||
if (selectedChannelClaimId && channelForEffect) {
|
||||
if (claimId && channelForEffect) {
|
||||
setFetchingStats(true);
|
||||
Lbryio.call('reports', 'content', { claim_id: selectedChannelClaimId })
|
||||
Lbryio.call('reports', 'content', { claim_id: claimId })
|
||||
.then(res => {
|
||||
setFetchingStats(false);
|
||||
setStats(res);
|
||||
})
|
||||
.catch(() => {
|
||||
const channelToSend = JSON.parse(channelForEffect);
|
||||
analytics.apiLogPublish(channelToSend);
|
||||
.catch(error => {
|
||||
if (error.response.status === 401) {
|
||||
setError(UNAUTHENTICATED_ERROR);
|
||||
const channelToSend = JSON.parse(channelForEffect);
|
||||
analytics.apiLogPublish(channelToSend);
|
||||
} else {
|
||||
setError(GENERIC_ERROR);
|
||||
}
|
||||
|
||||
setFetchingStats(false);
|
||||
});
|
||||
}
|
||||
}, [selectedChannelClaimId, channelForEffect, setFetchingStats, setStats]);
|
||||
}, [claimId, channelForEffect, setFetchingStats, setStats]);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="section">
|
||||
<ChannelSelector
|
||||
selectedChannelUrl={selectedChannelUrl}
|
||||
onChannelSelect={newChannelUrl => {
|
||||
setStats(null);
|
||||
setSelectedChannelUrl(newChannelUrl);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{fetchingStats && !stats && (
|
||||
{!stats && (
|
||||
<div className="main--empty">
|
||||
<Spinner delayed />
|
||||
{fetchingStats ? (
|
||||
<Spinner delayed />
|
||||
) : (
|
||||
<div>
|
||||
{error && (
|
||||
<Yrbl
|
||||
type="sad"
|
||||
title={error === GENERIC_ERROR ? __('No Stats Found') : __('Error Fetching Stats')}
|
||||
subtitle={
|
||||
error === GENERIC_ERROR
|
||||
? __(
|
||||
'There are no stats for this channel. Make sure you are signed in with the correct email and have data sharing turned on.'
|
||||
)
|
||||
: __(
|
||||
"You are not able to see this channel's stats. Make sure you are signed in with the correct email and have data sharing turned on."
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!error && (
|
||||
<Yrbl
|
||||
title={
|
||||
channelHasClaims
|
||||
? __('No recent publishes')
|
||||
: __("You haven't published anything with this channel yet!")
|
||||
}
|
||||
subtitle={
|
||||
<Button
|
||||
button="primary"
|
||||
label={__('Publish Something')}
|
||||
onClick={() => {
|
||||
if (claim) {
|
||||
prepareEdit(claim.name);
|
||||
history.push(`/$/${PAGES.PUBLISH}`);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!fetchingStats && !stats && (
|
||||
<section className="main--empty">
|
||||
<Yrbl
|
||||
title={__("You haven't published anything with this channel yet!")}
|
||||
subtitle={
|
||||
<Button
|
||||
button="primary"
|
||||
label={__('Publish Something')}
|
||||
onClick={() => {
|
||||
if (selectedChannelClaim) {
|
||||
prepareEdit(selectedChannelClaim.name);
|
||||
history.push(`/$/${PAGES.PUBLISH}`);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{stats && (
|
||||
<div className="section">
|
||||
<div className="columns">
|
||||
<Card
|
||||
iconColor
|
||||
title={<span>{stats.ChannelSubs} followers</span>}
|
||||
title={<span>{__('%follower_count% followers', { follower_count: stats.ChannelSubs })}</span>}
|
||||
icon={ICONS.SUBSCRIBE}
|
||||
subtitle={
|
||||
<div className="card__data-subtitle">
|
||||
|
@ -134,11 +143,81 @@ export default function CreatorAnalytics(props: Props) {
|
|||
</div>
|
||||
|
||||
<Card
|
||||
title={
|
||||
<div className="card__data-subtitle">
|
||||
<span>{__('Most Viewed Content')}</span>
|
||||
</div>
|
||||
iconColor
|
||||
className="section"
|
||||
title={<span>{__('%lbc_received% LBC Earned', { lbc_received: stats.AllLBCReceived })}</span>}
|
||||
icon={ICONS.SUBSCRIBE}
|
||||
subtitle={
|
||||
<React.Fragment>
|
||||
<div className="card__data-subtitle">
|
||||
<span>
|
||||
{'+'}{' '}
|
||||
{__('%lbc_received_changed% this week', {
|
||||
lbc_received_changed: stats.LBCReceivedChange || 0,
|
||||
})}
|
||||
</span>
|
||||
{stats.LBCReceivedChange > 0 && <Icon icon={ICONS.SUPPORT} iconColor="green" size={18} />}
|
||||
</div>
|
||||
<p className="help">
|
||||
{__(
|
||||
"Earnings may also include any LBC you've sent yourself or added as support. We are working on making this more accurate. Check your wallet page for the correct total balance."
|
||||
)}
|
||||
</p>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
|
||||
{stats.VideoURITopNew ? (
|
||||
<Card
|
||||
className="section"
|
||||
title={__('Your Recent Content')}
|
||||
body={
|
||||
<React.Fragment>
|
||||
<div className="card--inline">
|
||||
<ClaimPreview uri={stats.VideoViewsTopNew} />
|
||||
</div>
|
||||
<div className="section__subtitle card__data-subtitle">
|
||||
<span>
|
||||
{__('%view_count% %views%', {
|
||||
view_count: stats.VideoViewsTopNew,
|
||||
views: stats.VideoViewsTopNew === 1 ? 'view' : 'views',
|
||||
})}
|
||||
</span>
|
||||
{stats.VideoViewsTopNew > 0 && <Icon icon={ICONS.SUPPORT} iconColor="green" size={18} />}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Card
|
||||
className="section"
|
||||
title={__('Your Recent Content')}
|
||||
subtitle={
|
||||
!stats.VideoURITopNew &&
|
||||
__(
|
||||
"No recent publishes found for this channel. Publish something new and track how it's performing here."
|
||||
)
|
||||
}
|
||||
actions={
|
||||
<div className="section__actions">
|
||||
<Button
|
||||
button="primary"
|
||||
label={__('New Publish')}
|
||||
onClick={() => {
|
||||
if (claim) {
|
||||
prepareEdit(claim.name);
|
||||
history.push(`/$/${PAGES.PUBLISH}`);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Card
|
||||
className="section"
|
||||
title={__('Most Viewed Content')}
|
||||
body={
|
||||
<React.Fragment>
|
||||
<div className="card--inline">
|
||||
|
|
|
@ -4,6 +4,8 @@ import Page from 'component/page';
|
|||
import Spinner from 'component/spinner';
|
||||
import Button from 'component/button';
|
||||
import CreatorAnalytics from 'component/creatorAnalytics';
|
||||
import ChannelSelector from 'component/channelSelector';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
|
||||
type Props = {
|
||||
channels: Array<ChannelClaim>,
|
||||
|
@ -13,6 +15,22 @@ type Props = {
|
|||
|
||||
export default function CreatorDashboardPage(props: Props) {
|
||||
const { channels, fetchingChannels, openChannelCreateModal } = props;
|
||||
const [selectedChannelUrl, setSelectedChannelUrl] = usePersistedState('analytics-selected-channel');
|
||||
const hasChannels = channels && channels.length > 0;
|
||||
const firstChannel = hasChannels && channels[0];
|
||||
const firstChannelUrl = firstChannel && (firstChannel.canonical_url || firstChannel.permanent_url); // permanent_url is needed for pending publishes
|
||||
const channelFoundForSelectedChannelUrl =
|
||||
channels &&
|
||||
channels.find(channel => {
|
||||
return selectedChannelUrl === channel.canonical_url || selectedChannelUrl === channel.permanent_url;
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
// set default channel
|
||||
if ((!selectedChannelUrl || !channelFoundForSelectedChannelUrl) && firstChannelUrl) {
|
||||
setSelectedChannelUrl(firstChannelUrl);
|
||||
}
|
||||
}, [setSelectedChannelUrl, selectedChannelUrl, firstChannelUrl, channelFoundForSelectedChannelUrl]);
|
||||
|
||||
return (
|
||||
<Page>
|
||||
|
@ -33,7 +51,19 @@ export default function CreatorDashboardPage(props: Props) {
|
|||
</section>
|
||||
)}
|
||||
|
||||
{!fetchingChannels && channels && channels.length && <CreatorAnalytics />}
|
||||
{!fetchingChannels && channels && channels.length && (
|
||||
<React.Fragment>
|
||||
<div className="section">
|
||||
<ChannelSelector
|
||||
selectedChannelUrl={selectedChannelUrl}
|
||||
onChannelSelect={newChannelUrl => {
|
||||
setSelectedChannelUrl(newChannelUrl);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<CreatorAnalytics uri={selectedChannelUrl} />
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
}
|
||||
|
||||
.yrbl__content {
|
||||
max-width: 500px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.yrbl--first-run {
|
||||
|
|
Loading…
Add table
Reference in a new issue