diff --git a/package.json b/package.json index 2e7c64549..e7cbacb9e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "LBRY", - "version": "0.34.2", + "version": "0.35.0-rc.1", "description": "A browser for the LBRY network, a digital marketplace controlled by its users.", "keywords": [ "lbry" @@ -125,8 +125,8 @@ "jsmediatags": "^3.8.1", "json-loader": "^0.5.4", "lbry-format": "https://github.com/lbryio/lbry-format.git", - "lbry-redux": "lbryio/lbry-redux#05e70648e05c51c51710f6dd698a8e2219b54df2", - "lbryinc": "lbryio/lbryinc#a93596c51c8fb0a226cb84df04c26a6bb60a45fb", + "lbry-redux": "lbryio/lbry-redux#6005fa245a888e2de045d7e42411847de7943f52", + "lbryinc": "lbryio/lbryinc#1ce266b3c52654190b955e9c869b8e302aa5c585", "lint-staged": "^7.0.2", "localforage": "^1.7.1", "lodash-es": "^4.17.14", diff --git a/src/ui/analytics.js b/src/ui/analytics.js index 2c887eeaa..1806e40b1 100644 --- a/src/ui/analytics.js +++ b/src/ui/analytics.js @@ -3,6 +3,7 @@ import { Lbryio } from 'lbryinc'; import ReactGA from 'react-ga'; import { history } from './store'; // @if TARGET='app' +import Native from 'native'; import ElectronCookies from '@exponent/electron-cookies'; // @endif @@ -15,6 +16,9 @@ type Analytics = { apiLogView: (string, string, string, ?number, ?() => void) => void, apiLogPublish: () => void, tagFollowEvent: (string, boolean, string) => void, + emailProvidedEvent: () => void, + emailVerifiedEvent: () => void, + rewardEligibleEvent: () => void, }; let analyticsEnabled: boolean = true; @@ -26,11 +30,15 @@ const analytics: Analytics = { }, setUser: userId => { if (analyticsEnabled && userId) { - ReactGA.event({ - category: 'User', - action: 'userId', - value: userId, + ReactGA.set({ + userId, }); + + // @if TARGET='app' + Native.getAppVersionInfo().then(({ localVersion }) => { + sendGaEvent('Desktop-Version', localVersion); + }); + // @endif } }, toggle: (enabled: boolean): void => { @@ -39,8 +47,8 @@ const analytics: Analytics = { analyticsEnabled = enabled; // @endif }, - apiLogView: (uri, outpoint, claimId, timeToStart, onSuccessCb) => { - if (analyticsEnabled) { + apiLogView: (uri, outpoint, claimId, timeToStart) => { + if (analyticsEnabled && isProduction) { const params: { uri: string, outpoint: string, @@ -52,17 +60,12 @@ const analytics: Analytics = { claim_id: claimId, }; - if (timeToStart) { + // lbry.tv streams from AWS so we don't care about the time to start + if (timeToStart && !IS_WEB) { params.time_to_start = timeToStart; } - Lbryio.call('file', 'view', params) - .then(() => { - if (onSuccessCb) { - onSuccessCb(); - } - }) - .catch(() => {}); + Lbryio.call('file', 'view', params); } }, apiLogSearch: () => { @@ -82,23 +85,31 @@ const analytics: Analytics = { } }, tagFollowEvent: (tag, following, location) => { - if (analyticsEnabled) { - ReactGA.event({ - category: following ? 'Tag-Follow' : 'Tag-Unfollow', - action: tag, - }); - } + sendGaEvent(following ? 'Tag-Follow' : 'Tag-Unfollow', tag); }, channelBlockEvent: (uri, blocked, location) => { - if (analyticsEnabled) { - ReactGA.event({ - category: blocked ? 'Channel-Hidden' : 'Channel-Unhidden', - action: uri, - }); - } + sendGaEvent(blocked ? 'Channel-Hidden' : 'Channel-Unhidden', uri); + }, + emailProvidedEvent: () => { + sendGaEvent('Engagement', 'Email-Provided'); + }, + emailVerifiedEvent: () => { + sendGaEvent('Engagement', 'Email-Verified'); + }, + rewardEligibleEvent: () => { + sendGaEvent('Engagement', 'Reward-Eligible'); }, }; +function sendGaEvent(category, action) { + if (analyticsEnabled && isProduction) { + ReactGA.event({ + category, + action, + }); + } +} + // Initialize google analytics // Set `debug: true` for debug info // Will change once we have separate ids for desktop/web @@ -113,6 +124,8 @@ ElectronCookies.enable({ ReactGA.initialize(UA_ID, { testMode: process.env.NODE_ENV !== 'production', cookieDomain: 'auto', + // un-comment to see events as they are sent to google + // debug: true, }); // Manually call the first page view diff --git a/src/ui/component/app/view.jsx b/src/ui/component/app/view.jsx index 80501bce8..6740470de 100644 --- a/src/ui/component/app/view.jsx +++ b/src/ui/component/app/view.jsx @@ -12,6 +12,7 @@ import useKonamiListener from 'util/enhanced-layout'; import Yrbl from 'component/yrbl'; import FileViewer from 'component/fileViewer'; import { withRouter } from 'react-router'; +import usePrevious from 'util/use-previous'; export const MAIN_WRAPPER_CLASS = 'main-wrapper'; @@ -21,7 +22,7 @@ type Props = { language: string, theme: string, accessToken: ?string, - user: ?{ id: string, has_verified_email: boolean }, + user: ?{ id: string, has_verified_email: boolean, is_reward_approved: boolean }, location: { pathname: string }, fetchRewards: () => void, fetchRewardedContent: () => void, @@ -35,7 +36,10 @@ function App(props: Props) { const isEnhancedLayout = useKonamiListener(); const userId = user && user.id; const hasVerifiedEmail = user && user.has_verified_email; - + const isRewardApproved = user && user.is_reward_approved; + const previousUserId = usePrevious(userId); + const previousHasVerifiedEmail = usePrevious(hasVerifiedEmail); + const previousRewardApproved = usePrevious(isRewardApproved); const { pathname } = props.location; const urlParts = pathname.split('/'); const claimName = urlParts[1]; @@ -65,10 +69,24 @@ function App(props: Props) { }, [theme]); useEffect(() => { - if (userId) { + if (previousUserId === undefined && userId) { analytics.setUser(userId); } - }, [userId]); + }, [previousUserId, userId]); + + useEffect(() => { + // Check that previousHasVerifiedEmail was not undefined instead of just not truthy + // This ensures we don't fire the emailVerified event on the initial user fetch + if (previousHasVerifiedEmail !== undefined && hasVerifiedEmail) { + analytics.emailVerifiedEvent(); + } + }, [previousHasVerifiedEmail, hasVerifiedEmail]); + + useEffect(() => { + if (previousRewardApproved !== undefined && isRewardApproved) { + analytics.rewardEligibleEvent(); + } + }, [previousRewardApproved, isRewardApproved]); // @if TARGET='web' useEffect(() => { diff --git a/src/ui/component/claimPreview/index.js b/src/ui/component/claimPreview/index.js index 7b49dc36d..203d81718 100644 --- a/src/ui/component/claimPreview/index.js +++ b/src/ui/component/claimPreview/index.js @@ -1,3 +1,4 @@ +import * as PAGES from 'constants/pages'; import { connect } from 'react-redux'; import { doResolveUri, @@ -10,11 +11,15 @@ import { makeSelectClaimIsNsfw, selectBlockedChannels, selectChannelIsBlocked, + doClearPublish, + doPrepareEdit, } from 'lbry-redux'; import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc'; import { selectShowMatureContent } from 'redux/selectors/settings'; import { makeSelectHasVisitedUri } from 'redux/selectors/content'; import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions'; +import { push } from 'connected-react-router'; + import ClaimPreview from './view'; const select = (state, props) => ({ @@ -36,6 +41,11 @@ const select = (state, props) => ({ const perform = dispatch => ({ resolveUri: uri => dispatch(doResolveUri(uri)), + beginPublish: name => { + dispatch(doClearPublish()); + dispatch(doPrepareEdit({ name })); + dispatch(push(`/$/${PAGES.PUBLISH}`)); + }, }); export default connect( diff --git a/src/ui/component/claimPreview/view.jsx b/src/ui/component/claimPreview/view.jsx index d15ed42ad..93f142916 100644 --- a/src/ui/component/claimPreview/view.jsx +++ b/src/ui/component/claimPreview/view.jsx @@ -44,6 +44,7 @@ type Props = { blockedChannelUris: Array, channelIsBlocked: boolean, isSubscribed: boolean, + beginPublish: string => void, }; const ClaimPreview = forwardRef((props: Props, ref: any) => { @@ -68,6 +69,7 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { showUserBlocked, channelIsBlocked, isSubscribed, + beginPublish, } = props; const haventFetched = claim === undefined; const abandoned = !isResolvingUri && !claim; @@ -77,8 +79,9 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { const hideActions = type === 'small' || type === 'tooltip'; let isValid; + let name; try { - parseURI(uri); + ({ claimName: name } = parseURI(uri)); isValid = true; } catch (e) { isValid = false; @@ -191,7 +194,11 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => {
{__('Publish something and claim this spot!')}
-
)} diff --git a/src/ui/component/common/icon.jsx b/src/ui/component/common/icon.jsx index 480bc47f8..75e66b091 100644 --- a/src/ui/component/common/icon.jsx +++ b/src/ui/component/common/icon.jsx @@ -25,7 +25,7 @@ class IconComponent extends React.PureComponent { case ICONS.FEATURED: return __('Featured content. Earn rewards for watching.'); case ICONS.DOWNLOAD: - return __('This file is downloaded.'); + return __('This file is in your library.'); case ICONS.SUBSCRIBE: return __('You are subscribed to this channel.'); case ICONS.SETTINGS: diff --git a/src/ui/component/fileActions/view.jsx b/src/ui/component/fileActions/view.jsx index d15420fcb..ce3ac5dc5 100644 --- a/src/ui/component/fileActions/view.jsx +++ b/src/ui/component/fileActions/view.jsx @@ -16,9 +16,7 @@ type Props = { class FileActions extends React.PureComponent { render() { const { fileInfo, uri, openModal, claimIsMine, claimId } = this.props; - const showDelete = - claimIsMine || - (fileInfo && (fileInfo.written_bytes > 0 || fileInfo.blobs_completed === fileInfo.blobs_in_stream)); + const showDelete = claimIsMine || (fileInfo && (fileInfo.written_bytes > 0 || fileInfo.blobs_completed > 0)); return ( {showDelete && ( diff --git a/src/ui/component/fileDetails/view.jsx b/src/ui/component/fileDetails/view.jsx index 72cc8a869..8682359d5 100644 --- a/src/ui/component/fileDetails/view.jsx +++ b/src/ui/component/fileDetails/view.jsx @@ -77,6 +77,7 @@ class FileDetails extends PureComponent { {': '}