referrals round 2
This commit is contained in:
parent
b15f728d16
commit
93e8616e2e
23 changed files with 635 additions and 173 deletions
|
@ -28,6 +28,7 @@
|
||||||
"dev:web": "cd ./lbrytv && yarn dev",
|
"dev:web": "cd ./lbrytv && yarn dev",
|
||||||
"dev:web-server": "cross-env NODE_ENV=development yarn compile:web && concurrently \"cross-env NODE_ENV=development yarn compile:web --watch\" \"cd ./lbrytv && yarn dev:server\"",
|
"dev:web-server": "cross-env NODE_ENV=development yarn compile:web && concurrently \"cross-env NODE_ENV=development yarn compile:web --watch\" \"cd ./lbrytv && yarn dev:server\"",
|
||||||
"dev:internal-apis": "LBRY_API_URL='http://localhost:8080' yarn dev:electron",
|
"dev:internal-apis": "LBRY_API_URL='http://localhost:8080' yarn dev:electron",
|
||||||
|
"dev:iatv": "LBRY_API_URL='http://localhost:15400' SDK_API_URL='http://localhost:15100' yarn dev:web",
|
||||||
"run:web": "cross-env NODE_ENV=production yarn compile:web && node ./dist/web/server.js",
|
"run:web": "cross-env NODE_ENV=production yarn compile:web && node ./dist/web/server.js",
|
||||||
"pack": "electron-builder --dir",
|
"pack": "electron-builder --dir",
|
||||||
"dist": "electron-builder",
|
"dist": "electron-builder",
|
||||||
|
@ -67,7 +68,7 @@
|
||||||
"@babel/register": "^7.0.0",
|
"@babel/register": "^7.0.0",
|
||||||
"@exponent/electron-cookies": "^2.0.0",
|
"@exponent/electron-cookies": "^2.0.0",
|
||||||
"@hot-loader/react-dom": "^16.8",
|
"@hot-loader/react-dom": "^16.8",
|
||||||
"@lbry/components": "^3.0.5",
|
"@lbry/components": "^3.0.6",
|
||||||
"@reach/menu-button": "^0.1.18",
|
"@reach/menu-button": "^0.1.18",
|
||||||
"@reach/rect": "^0.2.1",
|
"@reach/rect": "^0.2.1",
|
||||||
"@reach/tabs": "^0.1.5",
|
"@reach/tabs": "^0.1.5",
|
||||||
|
@ -128,7 +129,7 @@
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||||
"lbry-redux": "lbryio/lbry-redux#a2be979986dc93be4c2c596846109f5394f64fa1",
|
"lbry-redux": "lbryio/lbry-redux#a2be979986dc93be4c2c596846109f5394f64fa1",
|
||||||
"lbryinc": "lbryio/lbryinc#6042c6f7bbf5fe7c6db2bd169f5b1c4558485c4c",
|
"lbryinc": "lbryio/lbryinc#1912aa1834f83a5a43e028327d35bd64cfba528e",
|
||||||
"lint-staged": "^7.0.2",
|
"lint-staged": "^7.0.2",
|
||||||
"localforage": "^1.7.1",
|
"localforage": "^1.7.1",
|
||||||
"lodash-es": "^4.17.14",
|
"lodash-es": "^4.17.14",
|
||||||
|
@ -158,8 +159,8 @@
|
||||||
"react-modal": "^3.1.7",
|
"react-modal": "^3.1.7",
|
||||||
"react-paginate": "^5.2.1",
|
"react-paginate": "^5.2.1",
|
||||||
"react-redux": "^6.0.1",
|
"react-redux": "^6.0.1",
|
||||||
"react-router": "^5.0.0",
|
"react-router": "^5.1.0",
|
||||||
"react-router-dom": "^5.0.0",
|
"react-router-dom": "^5.1.0",
|
||||||
"react-simplemde-editor": "^4.0.0",
|
"react-simplemde-editor": "^4.0.0",
|
||||||
"react-spring": "^8.0.20",
|
"react-spring": "^8.0.20",
|
||||||
"react-sticky-box": "^0.8.0",
|
"react-sticky-box": "^0.8.0",
|
||||||
|
|
|
@ -1,16 +1,31 @@
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import { hot } from 'react-hot-loader/root';
|
import { hot } from 'react-hot-loader/root';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectUser, doRewardList, doFetchAccessToken, selectGetSyncErrorMessage, selectUploadCount } from 'lbryinc';
|
import {
|
||||||
|
selectUser,
|
||||||
|
selectAccessToken,
|
||||||
|
doRewardList,
|
||||||
|
doFetchAccessToken,
|
||||||
|
selectGetSyncErrorMessage,
|
||||||
|
selectUploadCount,
|
||||||
|
selectUnclaimedRewards,
|
||||||
|
doUserSetReferrer,
|
||||||
|
} from 'lbryinc';
|
||||||
import { doFetchTransactions, doFetchChannelListMine } from 'lbry-redux';
|
import { doFetchTransactions, doFetchChannelListMine } from 'lbry-redux';
|
||||||
import { makeSelectClientSetting, selectLoadedLanguages, selectThemePath } from 'redux/selectors/settings';
|
import { makeSelectClientSetting, selectLoadedLanguages, selectThemePath } from 'redux/selectors/settings';
|
||||||
import { selectIsUpgradeAvailable, selectAutoUpdateDownloaded } from 'redux/selectors/app';
|
import { selectIsUpgradeAvailable, selectAutoUpdateDownloaded } from 'redux/selectors/app';
|
||||||
import { doSetLanguage } from 'redux/actions/settings';
|
import { doSetLanguage } from 'redux/actions/settings';
|
||||||
import { doDownloadUpgradeRequested, doSignIn, doSyncWithPreferences, doGetAndPopulatePreferences } from 'redux/actions/app';
|
import {
|
||||||
|
doDownloadUpgradeRequested,
|
||||||
|
doSignIn,
|
||||||
|
doSyncWithPreferences,
|
||||||
|
doGetAndPopulatePreferences,
|
||||||
|
} from 'redux/actions/app';
|
||||||
import App from './view';
|
import App from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
user: selectUser(state),
|
user: selectUser(state),
|
||||||
|
accessToken: selectAccessToken(state),
|
||||||
theme: selectThemePath(state),
|
theme: selectThemePath(state),
|
||||||
language: makeSelectClientSetting(SETTINGS.LANGUAGE)(state),
|
language: makeSelectClientSetting(SETTINGS.LANGUAGE)(state),
|
||||||
languages: selectLoadedLanguages(state),
|
languages: selectLoadedLanguages(state),
|
||||||
|
@ -19,6 +34,7 @@ const select = state => ({
|
||||||
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
|
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
|
||||||
syncError: selectGetSyncErrorMessage(state),
|
syncError: selectGetSyncErrorMessage(state),
|
||||||
uploadCount: selectUploadCount(state),
|
uploadCount: selectUploadCount(state),
|
||||||
|
rewards: selectUnclaimedRewards(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
@ -31,6 +47,7 @@ const perform = dispatch => ({
|
||||||
requestDownloadUpgrade: () => dispatch(doDownloadUpgradeRequested()),
|
requestDownloadUpgrade: () => dispatch(doDownloadUpgradeRequested()),
|
||||||
checkSync: () => dispatch(doSyncWithPreferences()),
|
checkSync: () => dispatch(doSyncWithPreferences()),
|
||||||
updatePreferences: () => dispatch(doGetAndPopulatePreferences()),
|
updatePreferences: () => dispatch(doGetAndPopulatePreferences()),
|
||||||
|
setReferrer: (referrer, doClaim) => dispatch(doUserSetReferrer(referrer, doClaim)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default hot(
|
export default hot(
|
||||||
|
|
|
@ -14,6 +14,7 @@ import FloatingViewer from 'component/floatingViewer';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import usePrevious from 'effects/use-previous';
|
import usePrevious from 'effects/use-previous';
|
||||||
import Nag from 'component/common/nag';
|
import Nag from 'component/common/nag';
|
||||||
|
import { rewards as REWARDS } from 'lbryinc';
|
||||||
// @if TARGET='web'
|
// @if TARGET='web'
|
||||||
import OpenInAppLink from 'component/openInAppLink';
|
import OpenInAppLink from 'component/openInAppLink';
|
||||||
import YoutubeWelcome from 'component/youtubeWelcome';
|
import YoutubeWelcome from 'component/youtubeWelcome';
|
||||||
|
@ -32,7 +33,7 @@ type Props = {
|
||||||
languages: Array<string>,
|
languages: Array<string>,
|
||||||
theme: string,
|
theme: string,
|
||||||
user: ?{ id: string, has_verified_email: boolean, is_reward_approved: boolean },
|
user: ?{ id: string, has_verified_email: boolean, is_reward_approved: boolean },
|
||||||
location: { pathname: string, hash: string },
|
location: { pathname: string, hash: string, search: string },
|
||||||
history: { push: string => void },
|
history: { push: string => void },
|
||||||
fetchRewards: () => void,
|
fetchRewards: () => void,
|
||||||
fetchTransactions: (number, number) => void,
|
fetchTransactions: (number, number) => void,
|
||||||
|
@ -40,7 +41,6 @@ type Props = {
|
||||||
fetchChannelListMine: () => void,
|
fetchChannelListMine: () => void,
|
||||||
signIn: () => void,
|
signIn: () => void,
|
||||||
requestDownloadUpgrade: () => void,
|
requestDownloadUpgrade: () => void,
|
||||||
fetchChannelListMine: () => void,
|
|
||||||
onSignedIn: () => void,
|
onSignedIn: () => void,
|
||||||
setLanguage: string => void,
|
setLanguage: string => void,
|
||||||
isUpgradeAvailable: boolean,
|
isUpgradeAvailable: boolean,
|
||||||
|
@ -50,8 +50,9 @@ type Props = {
|
||||||
syncEnabled: boolean,
|
syncEnabled: boolean,
|
||||||
uploadCount: number,
|
uploadCount: number,
|
||||||
balance: ?number,
|
balance: ?number,
|
||||||
accessToken: ?string,
|
|
||||||
syncError: ?string,
|
syncError: ?string,
|
||||||
|
rewards: Array<Reward>,
|
||||||
|
setReferrer: (string, boolean) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
function App(props: Props) {
|
function App(props: Props) {
|
||||||
|
@ -75,6 +76,8 @@ function App(props: Props) {
|
||||||
languages,
|
languages,
|
||||||
setLanguage,
|
setLanguage,
|
||||||
updatePreferences,
|
updatePreferences,
|
||||||
|
rewards,
|
||||||
|
setReferrer,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const appRef = useRef();
|
const appRef = useRef();
|
||||||
|
@ -86,8 +89,13 @@ function App(props: Props) {
|
||||||
const previousUserId = usePrevious(userId);
|
const previousUserId = usePrevious(userId);
|
||||||
const previousHasVerifiedEmail = usePrevious(hasVerifiedEmail);
|
const previousHasVerifiedEmail = usePrevious(hasVerifiedEmail);
|
||||||
const previousRewardApproved = usePrevious(isRewardApproved);
|
const previousRewardApproved = usePrevious(isRewardApproved);
|
||||||
const { pathname, hash } = props.location;
|
const { pathname, hash, search } = props.location;
|
||||||
const showUpgradeButton = autoUpdateDownloaded || (process.platform === 'linux' && isUpgradeAvailable);
|
const showUpgradeButton = autoUpdateDownloaded || (process.platform === 'linux' && isUpgradeAvailable);
|
||||||
|
// referral claiming
|
||||||
|
const referredRewardAvailable = rewards && rewards.some(reward => reward.reward_type === REWARDS.TYPE_REFEREE);
|
||||||
|
const urlParams = new URLSearchParams(search);
|
||||||
|
const rawReferrerParam = urlParams.get('r');
|
||||||
|
const sanitizedReferrerParam = rawReferrerParam && rawReferrerParam.replace(':', '#');
|
||||||
|
|
||||||
let uri;
|
let uri;
|
||||||
try {
|
try {
|
||||||
|
@ -105,6 +113,14 @@ function App(props: Props) {
|
||||||
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
|
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||||
}, [uploadCount]);
|
}, [uploadCount]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (referredRewardAvailable && sanitizedReferrerParam && isRewardApproved) {
|
||||||
|
setReferrer(sanitizedReferrerParam, true);
|
||||||
|
} else if (referredRewardAvailable && sanitizedReferrerParam) {
|
||||||
|
setReferrer(sanitizedReferrerParam, false);
|
||||||
|
}
|
||||||
|
}, [sanitizedReferrerParam, isRewardApproved, referredRewardAvailable]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ReactModal.setAppElement(appRef.current);
|
ReactModal.setAppElement(appRef.current);
|
||||||
fetchAccessToken();
|
fetchAccessToken();
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
makeSelectClaimIsMine,
|
makeSelectClaimIsMine,
|
||||||
makeSelectClaimIsPending,
|
makeSelectClaimIsPending,
|
||||||
makeSelectThumbnailForUri,
|
makeSelectThumbnailForUri,
|
||||||
|
makeSelectCoverForUri,
|
||||||
makeSelectTitleForUri,
|
makeSelectTitleForUri,
|
||||||
makeSelectClaimIsNsfw,
|
makeSelectClaimIsNsfw,
|
||||||
selectBlockedChannels,
|
selectBlockedChannels,
|
||||||
|
@ -32,6 +33,7 @@ const select = (state, props) => ({
|
||||||
claimIsMine: props.uri && makeSelectClaimIsMine(props.uri)(state),
|
claimIsMine: props.uri && makeSelectClaimIsMine(props.uri)(state),
|
||||||
isResolvingUri: props.uri && makeSelectIsUriResolving(props.uri)(state),
|
isResolvingUri: props.uri && makeSelectIsUriResolving(props.uri)(state),
|
||||||
thumbnail: props.uri && makeSelectThumbnailForUri(props.uri)(state),
|
thumbnail: props.uri && makeSelectThumbnailForUri(props.uri)(state),
|
||||||
|
cover: props.uri && makeSelectCoverForUri(props.uri)(state),
|
||||||
title: props.uri && makeSelectTitleForUri(props.uri)(state),
|
title: props.uri && makeSelectTitleForUri(props.uri)(state),
|
||||||
mediaType: makeSelectMediaTypeForUri(props.uri)(state),
|
mediaType: makeSelectMediaTypeForUri(props.uri)(state),
|
||||||
nsfw: props.uri && makeSelectClaimIsNsfw(props.uri)(state),
|
nsfw: props.uri && makeSelectClaimIsNsfw(props.uri)(state),
|
||||||
|
|
|
@ -183,10 +183,10 @@ const Header = (props: Props) => {
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
{/* Commented out until new invite system is implemented */}
|
{/* Commented out until new invite system is implemented */}
|
||||||
{/* <MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.INVITE}`)}>
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.INVITE}`)}>
|
||||||
<Icon aria-hidden icon={ICONS.INVITE} />
|
<Icon aria-hidden icon={ICONS.INVITE} />
|
||||||
{__('Invite A Friend')}
|
{__('Invites')}
|
||||||
</MenuItem> */}
|
</MenuItem>
|
||||||
|
|
||||||
{authenticated ? (
|
{authenticated ? (
|
||||||
<MenuItem onSelect={signOut}>
|
<MenuItem onSelect={signOut}>
|
||||||
|
|
|
@ -4,19 +4,33 @@ import {
|
||||||
selectUserInviteNewIsPending,
|
selectUserInviteNewIsPending,
|
||||||
selectUserInviteNewErrorMessage,
|
selectUserInviteNewErrorMessage,
|
||||||
selectUserInviteReferralLink,
|
selectUserInviteReferralLink,
|
||||||
|
selectUserInviteReferralCode,
|
||||||
doUserInviteNew,
|
doUserInviteNew,
|
||||||
} from 'lbryinc';
|
} from 'lbryinc';
|
||||||
|
import {
|
||||||
|
selectMyChannelClaims,
|
||||||
|
selectFetchingMyChannels,
|
||||||
|
doFetchChannelListMine,
|
||||||
|
doResolveUris,
|
||||||
|
selectResolvingUris,
|
||||||
|
} from 'lbry-redux';
|
||||||
import InviteNew from './view';
|
import InviteNew from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
errorMessage: selectUserInviteNewErrorMessage(state),
|
errorMessage: selectUserInviteNewErrorMessage(state),
|
||||||
invitesRemaining: selectUserInvitesRemaining(state),
|
invitesRemaining: selectUserInvitesRemaining(state),
|
||||||
referralLink: selectUserInviteReferralLink(state),
|
referralLink: selectUserInviteReferralLink(state),
|
||||||
|
referralCode: selectUserInviteReferralCode(state),
|
||||||
isPending: selectUserInviteNewIsPending(state),
|
isPending: selectUserInviteNewIsPending(state),
|
||||||
|
channels: selectMyChannelClaims(state),
|
||||||
|
fetchingChannels: selectFetchingMyChannels(state),
|
||||||
|
resolvingUris: selectResolvingUris(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
inviteNew: email => dispatch(doUserInviteNew(email)),
|
inviteNew: email => dispatch(doUserInviteNew(email)),
|
||||||
|
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
|
||||||
|
resolveUris: uris => dispatch(doResolveUris(uris)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
|
|
@ -1,71 +1,120 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import { Form, FormField } from 'component/common/form';
|
import { Form, FormField } from 'component/common/form';
|
||||||
import CopyableText from 'component/copyableText';
|
import CopyableText from 'component/copyableText';
|
||||||
import Card from 'component/common/card';
|
import Card from 'component/common/card';
|
||||||
|
import { URL } from 'config';
|
||||||
|
import SelectChannel from 'component/selectChannel';
|
||||||
|
import analytics from 'analytics';
|
||||||
import I18nMessage from 'component/i18nMessage';
|
import I18nMessage from 'component/i18nMessage';
|
||||||
|
|
||||||
type FormState = {
|
|
||||||
email: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
errorMessage: ?string,
|
errorMessage: ?string,
|
||||||
inviteNew: string => void,
|
inviteNew: string => void,
|
||||||
isPending: boolean,
|
isPending: boolean,
|
||||||
referralLink: string,
|
referralLink: string,
|
||||||
|
referralCode: string,
|
||||||
|
channels: ?Array<ChannelClaim>,
|
||||||
|
resolvingUris: Array<string>,
|
||||||
|
resolveUris: (Array<string>) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
class InviteNew extends React.PureComponent<Props, FormState> {
|
function InviteNew(props: Props) {
|
||||||
constructor() {
|
const { inviteNew, errorMessage, isPending, referralCode = '', channels, resolveUris, resolvingUris } = props;
|
||||||
super();
|
const rewardAmount = 20;
|
||||||
|
|
||||||
this.state = {
|
// Email
|
||||||
email: '',
|
const [email, setEmail] = useState('');
|
||||||
};
|
function handleSubmit() {
|
||||||
|
inviteNew(email);
|
||||||
(this: any).handleSubmit = this.handleSubmit.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEmailChanged(event: any) {
|
function handleEmailChanged(event: any) {
|
||||||
this.setState({
|
setEmail(event.target.value);
|
||||||
email: event.target.value,
|
}
|
||||||
|
|
||||||
|
// Referral link
|
||||||
|
const [referralSource, setReferralSource] = useState(referralCode);
|
||||||
|
/* Canonical Referral links
|
||||||
|
* We need to make sure our channels are resolved so that canonical_url is present
|
||||||
|
*/
|
||||||
|
|
||||||
|
function handleReferralChange(code) {
|
||||||
|
setReferralSource(code);
|
||||||
|
// TODO: keep track of this in an array?
|
||||||
|
const matchingChannel = channels && channels.find(ch => ch.name === code);
|
||||||
|
if (matchingChannel) {
|
||||||
|
analytics.apiLogPublish(matchingChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [resolveStarted, setResolveStarted] = useState(false);
|
||||||
|
const [hasResolved, setHasResolved] = useState(false);
|
||||||
|
// join them so that useEffect doesn't update on new objects
|
||||||
|
const uris = channels && channels.map(channel => channel.permanent_url).join(',');
|
||||||
|
const channelCount = channels && channels.length;
|
||||||
|
const resolvingCount = resolvingUris && resolvingUris.length;
|
||||||
|
|
||||||
|
const topChannel =
|
||||||
|
channels &&
|
||||||
|
channels.reduce((top, channel) => {
|
||||||
|
const topClaimCount = (top && top.meta && top.meta.claims_in_channel) || 0;
|
||||||
|
const currentClaimCount = (channel && channel.meta && channel.meta.claims_in_channel) || 0;
|
||||||
|
return topClaimCount >= currentClaimCount ? top : channel;
|
||||||
});
|
});
|
||||||
}
|
const referralString =
|
||||||
|
channels && channels.length && referralSource !== referralCode
|
||||||
|
? lookupUrlByClaimName(referralSource, channels)
|
||||||
|
: referralSource;
|
||||||
|
|
||||||
handleSubmit() {
|
const referral = `${URL}/$/invite/${referralString.replace('#', ':')}`;
|
||||||
const { email } = this.state;
|
|
||||||
this.props.inviteNew(email);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
useEffect(() => {
|
||||||
const { errorMessage, isPending, referralLink } = this.props;
|
// resolve once, after we have channel list
|
||||||
|
if (!hasResolved && !resolveStarted && channelCount && uris) {
|
||||||
|
setResolveStarted(true);
|
||||||
|
resolveUris(uris.split(','));
|
||||||
|
}
|
||||||
|
}, [channelCount, resolveStarted, hasResolved, resolvingCount, uris]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// once resolving count is 0, we know we're done
|
||||||
|
if (resolveStarted && !hasResolved && resolvingCount === 0) {
|
||||||
|
setHasResolved(true);
|
||||||
|
}
|
||||||
|
}, [resolveStarted, hasResolved, resolvingCount]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// set default channel
|
||||||
|
if (topChannel && hasResolved) {
|
||||||
|
handleReferralChange(topChannel.name);
|
||||||
|
}
|
||||||
|
}, [topChannel, hasResolved]);
|
||||||
|
|
||||||
|
function lookupUrlByClaimName(name, channels) {
|
||||||
|
const claim = channels.find(channel => channel.name === name);
|
||||||
|
return claim && claim.canonical_url ? claim.canonical_url.replace('lbry://', '') : name;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className={'columns'}>
|
||||||
<Card
|
<Card
|
||||||
title={__('Invite a Friend')}
|
title={__('Invite Link')}
|
||||||
subtitle={__('When your friends start using LBRY, the network gets stronger!')}
|
subtitle={__('Share this link with friends (or enemies) and get %reward_amount% LBC when they join lbry.tv', {
|
||||||
|
reward_amount: rewardAmount,
|
||||||
|
})}
|
||||||
actions={
|
actions={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Form onSubmit={this.handleSubmit}>
|
<CopyableText label={__('Your invite link')} copyable={referral} />
|
||||||
<FormField
|
<SelectChannel
|
||||||
type="text"
|
channel={referralSource}
|
||||||
label="Email"
|
onChannelChange={channel => handleReferralChange(channel)}
|
||||||
placeholder="youremail@example.org"
|
label={'Customize link'}
|
||||||
name="email"
|
hideAnon
|
||||||
value={this.state.email}
|
injected={[referralCode]}
|
||||||
error={errorMessage}
|
|
||||||
inputButton={
|
|
||||||
<Button button="secondary" type="submit" label="Invite" disabled={isPending || !this.state.email} />
|
|
||||||
}
|
|
||||||
onChange={event => {
|
|
||||||
this.handleEmailChanged(event);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CopyableText label={__('Or share this link with your friends')} copyable={referralLink} />
|
|
||||||
|
|
||||||
<p className="help">
|
<p className="help">
|
||||||
<I18nMessage
|
<I18nMessage
|
||||||
tokens={{
|
tokens={{
|
||||||
|
@ -73,16 +122,49 @@ class InviteNew extends React.PureComponent<Props, FormState> {
|
||||||
referral_faq_link: <Button button="link" label={__('FAQ')} href="https://lbry.com/faq/referrals" />,
|
referral_faq_link: <Button button="link" label={__('FAQ')} href="https://lbry.com/faq/referrals" />,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Earn %rewards_link% for inviting your friends. Read our %referral_faq_link% to learn more about
|
Earn %rewards_link% for inviting your friends. Read our %referral_faq_link% to learn more.
|
||||||
referrals.
|
</I18nMessage>
|
||||||
|
</p>
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
title={__('Invite by Email')}
|
||||||
|
subtitle={__('Invite someone you know by email and earn %reward_amount% LBC when they join lbry.tv.', {
|
||||||
|
reward_amount: rewardAmount,
|
||||||
|
})}
|
||||||
|
actions={
|
||||||
|
<React.Fragment>
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<FormField
|
||||||
|
type="text"
|
||||||
|
label="Email"
|
||||||
|
placeholder="youremail@example.org"
|
||||||
|
name="email"
|
||||||
|
value={email}
|
||||||
|
error={errorMessage}
|
||||||
|
inputButton={<Button button="secondary" type="submit" label="Invite" disabled={isPending || !email} />}
|
||||||
|
onChange={event => {
|
||||||
|
handleEmailChanged(event);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p className="help">
|
||||||
|
<I18nMessage
|
||||||
|
tokens={{
|
||||||
|
rewards_link: <Button button="link" navigate="/$/rewards" label={__('rewards')} />,
|
||||||
|
referral_faq_link: <Button button="link" label={__('FAQ')} href="https://lbry.com/faq/referrals" />,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Earn %rewards_link% for inviting your friends. Read our %referral_faq_link% to learn more.
|
||||||
</I18nMessage>
|
</I18nMessage>
|
||||||
</p>
|
</p>
|
||||||
</Form>
|
</Form>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default InviteNew;
|
export default InviteNew;
|
||||||
|
|
39
ui/component/invited/index.js
Normal file
39
ui/component/invited/index.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
selectUser,
|
||||||
|
doClaimRewardType,
|
||||||
|
doUserSetReferrer,
|
||||||
|
selectSetReferrerPending,
|
||||||
|
selectSetReferrerError,
|
||||||
|
rewards as REWARDS,
|
||||||
|
selectUnclaimedRewards,
|
||||||
|
} from 'lbryinc';
|
||||||
|
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
||||||
|
import { doChannelSubscribe } from 'redux/actions/subscriptions';
|
||||||
|
import Invited from './view';
|
||||||
|
import { withRouter } from 'react-router';
|
||||||
|
|
||||||
|
const select = (state, props) => {
|
||||||
|
return {
|
||||||
|
user: selectUser(state),
|
||||||
|
referrerSetPending: selectSetReferrerPending(state),
|
||||||
|
referrerSetError: selectSetReferrerError(state),
|
||||||
|
rewards: selectUnclaimedRewards(state),
|
||||||
|
isSubscribed: makeSelectIsSubscribed(props.fullUri)(state),
|
||||||
|
fullUri: props.fullUri,
|
||||||
|
referrer: props.referrer,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
claimReward: () => dispatch(doClaimRewardType(REWARDS.TYPE_REFEREE)),
|
||||||
|
setReferrer: referrer => dispatch(doUserSetReferrer(referrer)),
|
||||||
|
channelSubscribe: uri => dispatch(doChannelSubscribe(uri)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default withRouter(
|
||||||
|
connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(Invited)
|
||||||
|
);
|
173
ui/component/invited/view.jsx
Normal file
173
ui/component/invited/view.jsx
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
// @flow
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import ClaimPreview from 'component/claimPreview';
|
||||||
|
import Card from 'component/common/card';
|
||||||
|
import { parseURI } from 'lbry-redux';
|
||||||
|
import { rewards as REWARDS, ERRORS } from 'lbryinc';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
user: any,
|
||||||
|
claimReward: () => void,
|
||||||
|
setReferrer: string => void,
|
||||||
|
referrerSetPending: boolean,
|
||||||
|
referrerSetError: string,
|
||||||
|
channelSubscribe: (sub: Subscription) => void,
|
||||||
|
history: { push: string => void },
|
||||||
|
rewards: Array<Reward>,
|
||||||
|
referrer: string,
|
||||||
|
fullUri: string,
|
||||||
|
isSubscribed: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
function Invited(props: Props) {
|
||||||
|
const {
|
||||||
|
user,
|
||||||
|
claimReward,
|
||||||
|
setReferrer,
|
||||||
|
referrerSetPending,
|
||||||
|
referrerSetError,
|
||||||
|
channelSubscribe,
|
||||||
|
history,
|
||||||
|
rewards,
|
||||||
|
fullUri,
|
||||||
|
referrer,
|
||||||
|
isSubscribed,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const refUri = referrer && 'lbry://' + referrer.replace(':', '#');
|
||||||
|
const referrerIsChannel = parseURI(refUri).isChannel;
|
||||||
|
const rewardsApproved = user && user.is_reward_approved;
|
||||||
|
const hasVerifiedEmail = user && user.has_verified_email;
|
||||||
|
const referredRewardAvailable = rewards && rewards.some(reward => reward.reward_type === REWARDS.TYPE_REFEREE);
|
||||||
|
|
||||||
|
// always follow if it's a channel
|
||||||
|
useEffect(() => {
|
||||||
|
if (fullUri && !isSubscribed) {
|
||||||
|
channelSubscribe({
|
||||||
|
channelName: parseURI(fullUri).claimName,
|
||||||
|
uri: fullUri,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [fullUri, isSubscribed]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!referrerSetPending && hasVerifiedEmail) {
|
||||||
|
claimReward();
|
||||||
|
}
|
||||||
|
}, [referrerSetPending, hasVerifiedEmail]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (referrer) {
|
||||||
|
setReferrer(referrer.replace(':', '#'));
|
||||||
|
}
|
||||||
|
}, [referrer]);
|
||||||
|
|
||||||
|
function handleDone() {
|
||||||
|
history.push(`/$/${PAGES.DISCOVER}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (referrerSetError === ERRORS.ALREADY_CLAIMED) {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
title={__(`Whoa`)}
|
||||||
|
subtitle={
|
||||||
|
referrerIsChannel
|
||||||
|
? __(`You've already claimed your referrer, but we've followed this channel for you.`)
|
||||||
|
: __(`You've already claimed your referrer.`)
|
||||||
|
}
|
||||||
|
body={
|
||||||
|
referrerIsChannel && (
|
||||||
|
<div className="claim-preview--channel">
|
||||||
|
<ClaimPreview key={refUri} uri={refUri} actions={''} type={'small'} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
actions={
|
||||||
|
<div className="card__actions">
|
||||||
|
<Button button="primary" label={__('Done!')} onClick={handleDone} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (referrerSetError && referredRewardAvailable) {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
title={__(`Welcome!`)}
|
||||||
|
subtitle={__(
|
||||||
|
`Something went wrong with your invite link. You can set and claim your invite reward after signing in.`
|
||||||
|
)}
|
||||||
|
actions={
|
||||||
|
<>
|
||||||
|
<p className="error-text">{__('Not a valid invite')}</p>
|
||||||
|
<div className="card__actions">
|
||||||
|
<Button
|
||||||
|
button="primary"
|
||||||
|
label={hasVerifiedEmail ? __('Verify') : __('Create Account')}
|
||||||
|
navigate={`/$/${PAGES.AUTH}?redirect=/$/${PAGES.REWARDS}`}
|
||||||
|
/>
|
||||||
|
<Button button="link" label={__('Explore')} onClick={handleDone} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rewardsApproved) {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
title={__(`You're invited!`)}
|
||||||
|
subtitle={
|
||||||
|
referrerIsChannel
|
||||||
|
? __(
|
||||||
|
`Content freedom and and a present from %channel_name% are waiting for you. Create an account to claim it.`,
|
||||||
|
{ channel_name: referrer }
|
||||||
|
)
|
||||||
|
: __(`Content freedom and a present are waiting for you. Create an account to claim it.`)
|
||||||
|
}
|
||||||
|
body={
|
||||||
|
referrerIsChannel && (
|
||||||
|
<div className="claim-preview--channel">
|
||||||
|
<ClaimPreview key={refUri} uri={refUri} actions={''} type={'small'} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
actions={
|
||||||
|
<div className="card__actions">
|
||||||
|
<Button
|
||||||
|
button="primary"
|
||||||
|
label={hasVerifiedEmail ? __('Finish Account') : __('Create Account')}
|
||||||
|
navigate={`/$/${PAGES.AUTH}?redirect=/$/${PAGES.INVITE}/${referrer}`}
|
||||||
|
/>
|
||||||
|
<Button button="link" label={__('Skip')} onClick={handleDone} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
title={__(`Welcome!`)}
|
||||||
|
subtitle={referrerIsChannel ? __(`We've followed your invitee for you. Check them out!`) : __(`Congrats!`)}
|
||||||
|
body={
|
||||||
|
referrerIsChannel && (
|
||||||
|
<div className="claim-preview--channel">
|
||||||
|
<ClaimPreview key={refUri} uri={refUri} actions={''} type={'small'} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
actions={
|
||||||
|
<div className="card__actions">
|
||||||
|
<Button button="primary" label={__('Done')} onClick={handleDone} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Invited;
|
|
@ -2,12 +2,17 @@ import * as MODALS from 'constants/modal_types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doOpenModal } from 'redux/actions/app';
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
import RewardTile from './view';
|
import RewardTile from './view';
|
||||||
|
import { selectUser } from 'lbryinc';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
user: selectUser(state),
|
||||||
|
});
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
openRewardCodeModal: () => dispatch(doOpenModal(MODALS.REWARD_GENERATED_CODE)),
|
openRewardCodeModal: () => dispatch(doOpenModal(MODALS.REWARD_GENERATED_CODE)),
|
||||||
|
openSetReferrerModal: () => dispatch(doOpenModal(MODALS.SET_REFERRER)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
null,
|
select,
|
||||||
perform
|
perform
|
||||||
)(RewardTile);
|
)(RewardTile);
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { rewards } from 'lbryinc';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
openRewardCodeModal: () => void,
|
openRewardCodeModal: () => void,
|
||||||
|
openSetReferrerModal: () => void,
|
||||||
reward: {
|
reward: {
|
||||||
id: string,
|
id: string,
|
||||||
reward_title: string,
|
reward_title: string,
|
||||||
|
@ -19,10 +20,12 @@ type Props = {
|
||||||
reward_description: string,
|
reward_description: string,
|
||||||
reward_type: string,
|
reward_type: string,
|
||||||
},
|
},
|
||||||
|
user: User,
|
||||||
};
|
};
|
||||||
|
|
||||||
const RewardTile = (props: Props) => {
|
const RewardTile = (props: Props) => {
|
||||||
const { reward, openRewardCodeModal } = props;
|
const { reward, openRewardCodeModal, openSetReferrerModal, user } = props;
|
||||||
|
const referrerSet = user && user.invited_by_id;
|
||||||
const claimed = !!reward.transaction_id;
|
const claimed = !!reward.transaction_id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -35,7 +38,14 @@ const RewardTile = (props: Props) => {
|
||||||
<Button button="primary" onClick={openRewardCodeModal} label={__('Enter Code')} />
|
<Button button="primary" onClick={openRewardCodeModal} label={__('Enter Code')} />
|
||||||
)}
|
)}
|
||||||
{reward.reward_type === rewards.TYPE_REFERRAL && (
|
{reward.reward_type === rewards.TYPE_REFERRAL && (
|
||||||
<Button button="primary" navigate="/$/invite" label={__('Go To Invites')} />
|
<Button button="primary" navigate="/$/invite" label={__('Go to Invites')} />
|
||||||
|
)}
|
||||||
|
{reward.reward_type === rewards.TYPE_REFEREE && (
|
||||||
|
<Button
|
||||||
|
button="primary"
|
||||||
|
onClick={openSetReferrerModal}
|
||||||
|
label={referrerSet ? __('Change Invitee') : __('Set Invitee')}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{reward.reward_type !== rewards.TYPE_REFERRAL &&
|
{reward.reward_type !== rewards.TYPE_REFERRAL &&
|
||||||
(claimed ? (
|
(claimed ? (
|
||||||
|
|
|
@ -8,6 +8,7 @@ import ReportPage from 'page/report';
|
||||||
import ShowPage from 'page/show';
|
import ShowPage from 'page/show';
|
||||||
import PublishPage from 'page/publish';
|
import PublishPage from 'page/publish';
|
||||||
import DiscoverPage from 'page/discover';
|
import DiscoverPage from 'page/discover';
|
||||||
|
import InvitedPage from 'page/invited';
|
||||||
// import HomePage from 'page/home';
|
// import HomePage from 'page/home';
|
||||||
import RewardsPage from 'page/rewards';
|
import RewardsPage from 'page/rewards';
|
||||||
import FileListDownloaded from 'page/fileListDownloaded';
|
import FileListDownloaded from 'page/fileListDownloaded';
|
||||||
|
@ -89,6 +90,7 @@ function AppRouter(props: Props) {
|
||||||
<Route path={`/$/${PAGES.AUTH_VERIFY}`} exact component={SignInVerifyPage} />
|
<Route path={`/$/${PAGES.AUTH_VERIFY}`} exact component={SignInVerifyPage} />
|
||||||
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
|
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
|
||||||
<Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} />
|
<Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} />
|
||||||
|
<Route path={`/$/${PAGES.INVITE}/:referrer`} exact component={InvitedPage} />
|
||||||
|
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.INVITE}`} component={InvitePage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.INVITE}`} component={InvitePage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.DOWNLOADED}`} component={FileListDownloaded} />
|
<PrivateRoute {...props} path={`/$/${PAGES.DOWNLOADED}`} component={FileListDownloaded} />
|
||||||
|
|
|
@ -14,6 +14,10 @@ type Props = {
|
||||||
createChannel: (string, number) => Promise<any>,
|
createChannel: (string, number) => Promise<any>,
|
||||||
fetchChannelListMine: () => void,
|
fetchChannelListMine: () => void,
|
||||||
fetchingChannels: boolean,
|
fetchingChannels: boolean,
|
||||||
|
hideAnon: boolean,
|
||||||
|
includeNew?: boolean,
|
||||||
|
label?: string,
|
||||||
|
injected?: Array<string>,
|
||||||
emailVerified: boolean,
|
emailVerified: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -69,7 +73,7 @@ class ChannelSection extends React.PureComponent<Props, State> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const channel = this.state.addingChannel ? 'new' : this.props.channel;
|
const channel = this.state.addingChannel ? 'new' : this.props.channel;
|
||||||
const { fetchingChannels, channels = [] } = this.props;
|
const { fetchingChannels, channels = [], hideAnon, label, injected = [] } = this.props;
|
||||||
const { addingChannel } = this.state;
|
const { addingChannel } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -78,29 +82,30 @@ class ChannelSection extends React.PureComponent<Props, State> {
|
||||||
<BusyIndicator message="Updating channels" />
|
<BusyIndicator message="Updating channels" />
|
||||||
) : (
|
) : (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="section">
|
|
||||||
<FormField
|
<FormField
|
||||||
name="channel"
|
name="channel"
|
||||||
label={__('Channel')}
|
label={label || __('Channel')}
|
||||||
type="select"
|
type="select"
|
||||||
onChange={this.handleChannelChange}
|
onChange={this.handleChannelChange}
|
||||||
value={channel}
|
value={channel}
|
||||||
>
|
>
|
||||||
<option value={CHANNEL_ANONYMOUS}>{__('Anonymous')}</option>
|
{!hideAnon && <option value={CHANNEL_ANONYMOUS}>{__('Anonymous')}</option>}
|
||||||
{channels &&
|
{channels &&
|
||||||
channels.map(({ name, claim_id: claimId }) => (
|
channels.map(({ name, claim_id: claimId }) => (
|
||||||
<option key={claimId} value={name}>
|
<option key={claimId} value={name}>
|
||||||
{name}
|
{name}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
<option value={CHANNEL_NEW}>{__('New channel...')}</option>
|
{injected &&
|
||||||
|
injected.map(item => (
|
||||||
|
<option key={item} value={item}>
|
||||||
|
{item}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
<option value={CHANNEL_NEW}>{__('New channel...')}</option>}
|
||||||
</FormField>
|
</FormField>
|
||||||
</div>
|
|
||||||
{addingChannel && (
|
{addingChannel && <ChannelCreate onSuccess={this.handleChangeToNewChannel} />}
|
||||||
<div className="section">
|
|
||||||
<ChannelCreate onSuccess={this.handleChangeToNewChannel} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { makeSelectClaimForUri } from 'lbry-redux';
|
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||||
import SocialShare from './view';
|
import SocialShare from './view';
|
||||||
|
import { selectUserInviteReferralCode, selectUser } from 'lbryinc';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
|
referralCode: selectUserInviteReferralCode(state),
|
||||||
|
user: selectUser(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select)(SocialShare);
|
export default connect(select)(SocialShare);
|
||||||
|
|
|
@ -9,6 +9,8 @@ type Props = {
|
||||||
claim: Claim,
|
claim: Claim,
|
||||||
webShareable: boolean,
|
webShareable: boolean,
|
||||||
isChannel: boolean,
|
isChannel: boolean,
|
||||||
|
referralCode: string,
|
||||||
|
user: any,
|
||||||
};
|
};
|
||||||
|
|
||||||
class SocialShare extends React.PureComponent<Props> {
|
class SocialShare extends React.PureComponent<Props> {
|
||||||
|
@ -25,14 +27,16 @@ class SocialShare extends React.PureComponent<Props> {
|
||||||
input: ?HTMLInputElement;
|
input: ?HTMLInputElement;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { claim, isChannel } = this.props;
|
const { claim, isChannel, referralCode, user } = this.props;
|
||||||
const { canonical_url: canonicalUrl, permanent_url: permanentUrl } = claim;
|
const { canonical_url: canonicalUrl, permanent_url: permanentUrl } = claim;
|
||||||
const { webShareable } = this.props;
|
const { webShareable } = this.props;
|
||||||
|
const rewardsApproved = user && user.is_reward_approved;
|
||||||
const OPEN_URL = 'https://open.lbry.com/';
|
const OPEN_URL = 'https://open.lbry.com/';
|
||||||
const lbryUrl = canonicalUrl ? canonicalUrl.split('lbry://')[1] : permanentUrl.split('lbry://')[1];
|
const lbryUrl = canonicalUrl ? canonicalUrl.split('lbry://')[1] : permanentUrl.split('lbry://')[1];
|
||||||
const lbryWebUrl = lbryUrl.replace(/#/g, ':');
|
const lbryWebUrl = lbryUrl.replace(/#/g, ':');
|
||||||
const encodedLbryURL: string = `${OPEN_URL}${encodeURIComponent(lbryWebUrl)}`;
|
const encodedLbryURL: string = `${OPEN_URL}${encodeURIComponent(lbryWebUrl)}`;
|
||||||
const lbryURL: string = `${OPEN_URL}${lbryWebUrl}`;
|
const referralParam: string = referralCode && rewardsApproved ? `?r=${referralCode}` : '';
|
||||||
|
const lbryURL: string = `${OPEN_URL}${lbryWebUrl}${referralParam}`;
|
||||||
|
|
||||||
const shareOnFb = __('Share on Facebook');
|
const shareOnFb = __('Share on Facebook');
|
||||||
const shareOnTwitter = __('Share On Twitter');
|
const shareOnTwitter = __('Share On Twitter');
|
||||||
|
|
|
@ -34,3 +34,4 @@ export const WALLET_RECEIVE = 'wallet_receive';
|
||||||
export const CREATE_CHANNEL = 'create_channel';
|
export const CREATE_CHANNEL = 'create_channel';
|
||||||
export const YOUTUBE_WELCOME = 'youtube_welcome';
|
export const YOUTUBE_WELCOME = 'youtube_welcome';
|
||||||
export const MOBILE_NAVIGATION = 'mobile_navigation';
|
export const MOBILE_NAVIGATION = 'mobile_navigation';
|
||||||
|
export const SET_REFERRER = 'set_referrer';
|
||||||
|
|
|
@ -33,6 +33,7 @@ import ModalWalletReceive from 'modal/modalWalletReceive';
|
||||||
import ModalYoutubeWelcome from 'modal/modalYoutubeWelcome';
|
import ModalYoutubeWelcome from 'modal/modalYoutubeWelcome';
|
||||||
import ModalCreateChannel from 'modal/modalChannelCreate';
|
import ModalCreateChannel from 'modal/modalChannelCreate';
|
||||||
import ModalMobileNavigation from 'modal/modalMobileNavigation';
|
import ModalMobileNavigation from 'modal/modalMobileNavigation';
|
||||||
|
import ModalSetReferrer from 'modal/modalSetReferrer';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
modal: { id: string, modalProps: {} },
|
modal: { id: string, modalProps: {} },
|
||||||
|
@ -122,6 +123,8 @@ function ModalRouter(props: Props) {
|
||||||
return <ModalCreateChannel {...modalProps} />;
|
return <ModalCreateChannel {...modalProps} />;
|
||||||
case MODALS.MOBILE_NAVIGATION:
|
case MODALS.MOBILE_NAVIGATION:
|
||||||
return <ModalMobileNavigation {...modalProps} />;
|
return <ModalMobileNavigation {...modalProps} />;
|
||||||
|
case MODALS.SET_REFERRER:
|
||||||
|
return <ModalSetReferrer {...modalProps} />;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
19
ui/modal/modalSetReferrer/index.js
Normal file
19
ui/modal/modalSetReferrer/index.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { doHideModal } from 'redux/actions/app';
|
||||||
|
import { doUserSetReferrer, selectSetReferrerError, selectSetReferrerPending } from 'lbryinc';
|
||||||
|
import ModalSetReferrer from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
referrerSetPending: selectSetReferrerPending(state),
|
||||||
|
referrerSetError: selectSetReferrerError(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
closeModal: () => dispatch(doHideModal()),
|
||||||
|
setReferrer: (referrer, doClaim) => dispatch(doUserSetReferrer(referrer, doClaim)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(ModalSetReferrer);
|
71
ui/modal/modalSetReferrer/view.jsx
Normal file
71
ui/modal/modalSetReferrer/view.jsx
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react';
|
||||||
|
import { FormField, Form } from 'component/common/form';
|
||||||
|
import { Modal } from 'modal/modal';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import HelpLink from 'component/common/help-link';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
closeModal: () => void,
|
||||||
|
error: ?string,
|
||||||
|
rewardIsPending: boolean,
|
||||||
|
setReferrer: (string, boolean) => void,
|
||||||
|
referrerSetPending: boolean,
|
||||||
|
referrerSetError?: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
referrer: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
class ModalSetReferrer extends React.PureComponent<Props, State> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
referrer: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
(this: any).handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit() {
|
||||||
|
const { referrer } = this.state;
|
||||||
|
const { setReferrer } = this.props;
|
||||||
|
setReferrer(referrer, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { closeModal, rewardIsPending } = this.props;
|
||||||
|
const { referrer } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen title={__('Enter Invitee')} contentLabel={__('Enter Invitee')} type="custom" onAborted={closeModal}>
|
||||||
|
<Form onSubmit={this.handleSubmit}>
|
||||||
|
<p>
|
||||||
|
{__('Did someone invite you to use lbry.tv? Tell us who and you both get a reward!')}
|
||||||
|
<HelpLink href="https://lbry.com/faq/referrals" />.
|
||||||
|
</p>
|
||||||
|
<FormField
|
||||||
|
autoFocus
|
||||||
|
type="text"
|
||||||
|
name="referrer-code"
|
||||||
|
inputButton={
|
||||||
|
<Button button="primary" type="submit" disabled={!referrer || rewardIsPending} label={__('Set')} />
|
||||||
|
}
|
||||||
|
label={__('Code or channel')}
|
||||||
|
placeholder="0123abc"
|
||||||
|
value={referrer}
|
||||||
|
onChange={e => this.setState({ referrer: e.target.value })}
|
||||||
|
/>
|
||||||
|
</Form>
|
||||||
|
<div className="card__actions">
|
||||||
|
<Button button="primary" label={__('Done')} onClick={closeModal} />
|
||||||
|
<Button button="link" label={__('Close')} onClick={closeModal} />
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModalSetReferrer;
|
24
ui/page/invited/index.js
Normal file
24
ui/page/invited/index.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import InvitedPage from './view';
|
||||||
|
import { makeSelectPermanentUrlForUri } from 'lbry-redux';
|
||||||
|
import { withRouter } from 'react-router';
|
||||||
|
|
||||||
|
const select = (state, props) => {
|
||||||
|
const { match } = props;
|
||||||
|
const { params } = match;
|
||||||
|
const { referrer } = params;
|
||||||
|
const sanitizedReferrer = referrer ? referrer.replace(':', '#') : '';
|
||||||
|
const uri = `lbry://${sanitizedReferrer}`;
|
||||||
|
return {
|
||||||
|
fullUri: makeSelectPermanentUrlForUri(uri)(state),
|
||||||
|
referrer: referrer,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const perform = () => ({});
|
||||||
|
|
||||||
|
export default withRouter(
|
||||||
|
connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(InvitedPage)
|
||||||
|
);
|
18
ui/page/invited/view.jsx
Normal file
18
ui/page/invited/view.jsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import Page from 'component/page';
|
||||||
|
import Invited from 'component/invited';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
fullUri: string,
|
||||||
|
referrer: string,
|
||||||
|
};
|
||||||
|
export default function ReferredPage(props: Props) {
|
||||||
|
const { fullUri, referrer } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page authPage className="main--auth-page">
|
||||||
|
<Invited fullUri={fullUri} referrer={referrer} />
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
}
|
|
@ -21,7 +21,10 @@ export default handleActions(
|
||||||
[ACTIONS.CHANNEL_SUBSCRIBE]: (state: SubscriptionState, action: DoChannelSubscribe): SubscriptionState => {
|
[ACTIONS.CHANNEL_SUBSCRIBE]: (state: SubscriptionState, action: DoChannelSubscribe): SubscriptionState => {
|
||||||
const newSubscription: Subscription = action.data;
|
const newSubscription: Subscription = action.data;
|
||||||
const newSubscriptions: Array<Subscription> = state.subscriptions.slice();
|
const newSubscriptions: Array<Subscription> = state.subscriptions.slice();
|
||||||
|
// prevent duplicates in the sidebar
|
||||||
|
if (!newSubscriptions.some(sub => sub.uri === newSubscription.uri)) {
|
||||||
newSubscriptions.unshift(newSubscription);
|
newSubscriptions.unshift(newSubscription);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
130
yarn.lock
130
yarn.lock
|
@ -902,6 +902,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.2"
|
regenerator-runtime "^0.13.2"
|
||||||
|
|
||||||
|
"@babel/runtime@^7.4.0":
|
||||||
|
version "7.7.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.7.tgz#194769ca8d6d7790ec23605af9ee3e42a0aa79cf"
|
||||||
|
integrity sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime "^0.13.2"
|
||||||
|
|
||||||
"@babel/runtime@^7.6.3":
|
"@babel/runtime@^7.6.3":
|
||||||
version "7.7.4"
|
version "7.7.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.4.tgz#b23a856751e4bf099262f867767889c0e3fe175b"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.4.tgz#b23a856751e4bf099262f867767889c0e3fe175b"
|
||||||
|
@ -1019,10 +1026,10 @@
|
||||||
prop-types "^15.6.2"
|
prop-types "^15.6.2"
|
||||||
scheduler "^0.15.0"
|
scheduler "^0.15.0"
|
||||||
|
|
||||||
"@lbry/components@^3.0.5":
|
"@lbry/components@^3.0.6":
|
||||||
version "3.0.5"
|
version "3.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/@lbry/components/-/components-3.0.5.tgz#4ab6cf8f97113e4c2c90fb6a840801c9da11923f"
|
resolved "https://registry.yarnpkg.com/@lbry/components/-/components-3.0.6.tgz#e8040cd3025562eefaf80bb19a41a89637bb25cb"
|
||||||
integrity sha512-u0J5MY3JvGxPjusVQVtoWKUbTAokC6wy+zafq/qJdnCUtjJbEG57jx7sx6KSfdoCz7jqmr2bivAbzMx+oP2mzA==
|
integrity sha512-SJc0nJogFpIAIv+L75Rfw0BeQRBfDNqpH04ZbJUqQlFwk2yE73KdC1LSYoj8+S4j+quO8ELBqOm9OWqylL4sEg==
|
||||||
|
|
||||||
"@mapbox/hast-util-table-cell-style@^0.1.3":
|
"@mapbox/hast-util-table-cell-style@^0.1.3":
|
||||||
version "0.1.3"
|
version "0.1.3"
|
||||||
|
@ -1685,11 +1692,6 @@ array-unique@^0.3.2:
|
||||||
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
|
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
|
||||||
integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
|
integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
|
||||||
|
|
||||||
asap@~2.0.3:
|
|
||||||
version "2.0.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
|
|
||||||
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
|
|
||||||
|
|
||||||
asn1.js@^4.0.0:
|
asn1.js@^4.0.0:
|
||||||
version "4.10.1"
|
version "4.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0"
|
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0"
|
||||||
|
@ -3148,11 +3150,6 @@ core-js-compat@^3.1.1:
|
||||||
browserslist "^4.7.2"
|
browserslist "^4.7.2"
|
||||||
semver "^6.3.0"
|
semver "^6.3.0"
|
||||||
|
|
||||||
core-js@^1.0.0:
|
|
||||||
version "1.2.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
|
|
||||||
integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=
|
|
||||||
|
|
||||||
core-js@^2.4.0, core-js@^2.6.5:
|
core-js@^2.4.0, core-js@^2.6.5:
|
||||||
version "2.6.8"
|
version "2.6.8"
|
||||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.8.tgz#dc3a1e633a04267944e0cb850d3880f340248139"
|
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.8.tgz#dc3a1e633a04267944e0cb850d3880f340248139"
|
||||||
|
@ -3239,14 +3236,6 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
|
||||||
safe-buffer "^5.0.1"
|
safe-buffer "^5.0.1"
|
||||||
sha.js "^2.4.8"
|
sha.js "^2.4.8"
|
||||||
|
|
||||||
create-react-context@^0.2.2:
|
|
||||||
version "0.2.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.3.tgz#9ec140a6914a22ef04b8b09b7771de89567cb6f3"
|
|
||||||
integrity sha512-CQBmD0+QGgTaxDL3OX1IDXYqjkp2It4RIbcb99jS6AEg27Ga+a9G3JtK6SIu0HBwPLZlmwt9F7UwWA4Bn92Rag==
|
|
||||||
dependencies:
|
|
||||||
fbjs "^0.8.0"
|
|
||||||
gud "^1.0.0"
|
|
||||||
|
|
||||||
crocket@^0.9.11:
|
crocket@^0.9.11:
|
||||||
version "0.9.11"
|
version "0.9.11"
|
||||||
resolved "https://registry.yarnpkg.com/crocket/-/crocket-0.9.11.tgz#288fca11ef0d3dd239b62c488265f30c8edfb0c5"
|
resolved "https://registry.yarnpkg.com/crocket/-/crocket-0.9.11.tgz#288fca11ef0d3dd239b62c488265f30c8edfb0c5"
|
||||||
|
@ -4301,13 +4290,6 @@ encodeurl@~1.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||||
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
|
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
|
||||||
|
|
||||||
encoding@^0.1.11:
|
|
||||||
version "0.1.12"
|
|
||||||
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
|
|
||||||
integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=
|
|
||||||
dependencies:
|
|
||||||
iconv-lite "~0.4.13"
|
|
||||||
|
|
||||||
end-of-stream@^1.0.0, end-of-stream@^1.1.0:
|
end-of-stream@^1.0.0, end-of-stream@^1.1.0:
|
||||||
version "1.4.4"
|
version "1.4.4"
|
||||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
|
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
|
||||||
|
@ -4984,19 +4966,6 @@ faye-websocket@~0.11.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
websocket-driver ">=0.5.1"
|
websocket-driver ">=0.5.1"
|
||||||
|
|
||||||
fbjs@^0.8.0:
|
|
||||||
version "0.8.17"
|
|
||||||
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
|
|
||||||
integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=
|
|
||||||
dependencies:
|
|
||||||
core-js "^1.0.0"
|
|
||||||
isomorphic-fetch "^2.1.1"
|
|
||||||
loose-envify "^1.0.0"
|
|
||||||
object-assign "^4.1.0"
|
|
||||||
promise "^7.1.1"
|
|
||||||
setimmediate "^1.0.5"
|
|
||||||
ua-parser-js "^0.7.18"
|
|
||||||
|
|
||||||
fd-slicer@~1.0.1:
|
fd-slicer@~1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
|
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
|
||||||
|
@ -6088,7 +6057,7 @@ husky@^3.1.0:
|
||||||
run-node "^1.0.0"
|
run-node "^1.0.0"
|
||||||
slash "^3.0.0"
|
slash "^3.0.0"
|
||||||
|
|
||||||
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13:
|
iconv-lite@0.4.24, iconv-lite@^0.4.24:
|
||||||
version "0.4.24"
|
version "0.4.24"
|
||||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||||
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||||
|
@ -6725,7 +6694,7 @@ is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
|
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
|
||||||
integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=
|
integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=
|
||||||
|
|
||||||
is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0:
|
is-stream@^1.0.0, is-stream@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||||
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
|
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
|
||||||
|
@ -6825,14 +6794,6 @@ isobject@^3.0.0, isobject@^3.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||||
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
|
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
|
||||||
|
|
||||||
isomorphic-fetch@^2.1.1:
|
|
||||||
version "2.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
|
|
||||||
integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=
|
|
||||||
dependencies:
|
|
||||||
node-fetch "^1.0.1"
|
|
||||||
whatwg-fetch ">=0.10.0"
|
|
||||||
|
|
||||||
isstream@~0.1.2:
|
isstream@~0.1.2:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||||
|
@ -7110,9 +7071,9 @@ lbry-redux@lbryio/lbry-redux#a2be979986dc93be4c2c596846109f5394f64fa1:
|
||||||
reselect "^3.0.0"
|
reselect "^3.0.0"
|
||||||
uuid "^3.3.2"
|
uuid "^3.3.2"
|
||||||
|
|
||||||
lbryinc@lbryio/lbryinc#6042c6f7bbf5fe7c6db2bd169f5b1c4558485c4c:
|
lbryinc@lbryio/lbryinc#e7e800b2d2d27ff7fed6d66cba40dd0149fee50c:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/6042c6f7bbf5fe7c6db2bd169f5b1c4558485c4c"
|
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/e7e800b2d2d27ff7fed6d66cba40dd0149fee50c"
|
||||||
dependencies:
|
dependencies:
|
||||||
reselect "^3.0.0"
|
reselect "^3.0.0"
|
||||||
|
|
||||||
|
@ -7810,6 +7771,15 @@ min-document@^2.19.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
dom-walk "^0.1.0"
|
dom-walk "^0.1.0"
|
||||||
|
|
||||||
|
mini-create-react-context@^0.3.0:
|
||||||
|
version "0.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz#79fc598f283dd623da8e088b05db8cddab250189"
|
||||||
|
integrity sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.4.0"
|
||||||
|
gud "^1.0.0"
|
||||||
|
tiny-warning "^1.0.2"
|
||||||
|
|
||||||
mini-css-extract-plugin@^0.7.0:
|
mini-css-extract-plugin@^0.7.0:
|
||||||
version "0.7.0"
|
version "0.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.7.0.tgz#5ba8290fbb4179a43dd27cca444ba150bee743a0"
|
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.7.0.tgz#5ba8290fbb4179a43dd27cca444ba150bee743a0"
|
||||||
|
@ -8050,14 +8020,6 @@ node-emoji@^1.8.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash.toarray "^4.4.0"
|
lodash.toarray "^4.4.0"
|
||||||
|
|
||||||
node-fetch@^1.0.1:
|
|
||||||
version "1.7.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
|
|
||||||
integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==
|
|
||||||
dependencies:
|
|
||||||
encoding "^0.1.11"
|
|
||||||
is-stream "^1.0.1"
|
|
||||||
|
|
||||||
node-fetch@^2.1.1, node-fetch@^2.3.0:
|
node-fetch@^2.1.1, node-fetch@^2.3.0:
|
||||||
version "2.6.0"
|
version "2.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
|
||||||
|
@ -9747,13 +9709,6 @@ promise-inflight@^1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
|
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
|
||||||
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
|
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
|
||||||
|
|
||||||
promise@^7.1.1:
|
|
||||||
version "7.3.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
|
|
||||||
integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
|
|
||||||
dependencies:
|
|
||||||
asap "~2.0.3"
|
|
||||||
|
|
||||||
prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
|
prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||||
version "15.7.2"
|
version "15.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||||
|
@ -10106,29 +10061,29 @@ react-redux@^6.0.1:
|
||||||
prop-types "^15.7.2"
|
prop-types "^15.7.2"
|
||||||
react-is "^16.8.2"
|
react-is "^16.8.2"
|
||||||
|
|
||||||
react-router-dom@^5.0.0:
|
react-router-dom@^5.1.0:
|
||||||
version "5.0.0"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.0.0.tgz#542a9b86af269a37f0b87218c4c25ea8dcf0c073"
|
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18"
|
||||||
integrity sha512-wSpja5g9kh5dIteZT3tUoggjnsa+TPFHSMrpHXMpFsaHhQkm/JNVGh2jiF9Dkh4+duj4MKCkwO6H08u6inZYgQ==
|
integrity sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.1.2"
|
"@babel/runtime" "^7.1.2"
|
||||||
history "^4.9.0"
|
history "^4.9.0"
|
||||||
loose-envify "^1.3.1"
|
loose-envify "^1.3.1"
|
||||||
prop-types "^15.6.2"
|
prop-types "^15.6.2"
|
||||||
react-router "5.0.0"
|
react-router "5.1.2"
|
||||||
tiny-invariant "^1.0.2"
|
tiny-invariant "^1.0.2"
|
||||||
tiny-warning "^1.0.0"
|
tiny-warning "^1.0.0"
|
||||||
|
|
||||||
react-router@5.0.0, react-router@^5.0.0:
|
react-router@5.1.2, react-router@^5.1.0:
|
||||||
version "5.0.0"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.0.0.tgz#349863f769ffc2fa10ee7331a4296e86bc12879d"
|
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418"
|
||||||
integrity sha512-6EQDakGdLG/it2x9EaCt9ZpEEPxnd0OCLBHQ1AcITAAx7nCnyvnzf76jKWG1s2/oJ7SSviUgfWHofdYljFexsA==
|
integrity sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.1.2"
|
"@babel/runtime" "^7.1.2"
|
||||||
create-react-context "^0.2.2"
|
|
||||||
history "^4.9.0"
|
history "^4.9.0"
|
||||||
hoist-non-react-statics "^3.1.0"
|
hoist-non-react-statics "^3.1.0"
|
||||||
loose-envify "^1.3.1"
|
loose-envify "^1.3.1"
|
||||||
|
mini-create-react-context "^0.3.0"
|
||||||
path-to-regexp "^1.7.0"
|
path-to-regexp "^1.7.0"
|
||||||
prop-types "^15.6.2"
|
prop-types "^15.6.2"
|
||||||
react-is "^16.6.0"
|
react-is "^16.6.0"
|
||||||
|
@ -11056,7 +11011,7 @@ set-value@^2.0.0, set-value@^2.0.1:
|
||||||
is-plain-object "^2.0.3"
|
is-plain-object "^2.0.3"
|
||||||
split-string "^3.0.1"
|
split-string "^3.0.1"
|
||||||
|
|
||||||
setimmediate@^1.0.4, setimmediate@^1.0.5, setimmediate@~1.0.4:
|
setimmediate@^1.0.4, setimmediate@~1.0.4:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
||||||
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
|
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
|
||||||
|
@ -11989,6 +11944,11 @@ tiny-warning@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.2.tgz#1dfae771ee1a04396bdfde27a3adcebc6b648b28"
|
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.2.tgz#1dfae771ee1a04396bdfde27a3adcebc6b648b28"
|
||||||
integrity sha512-rru86D9CpQRLvsFG5XFdy0KdLAvjdQDyZCsRcuu60WtzFylDM3eAWSxEVz5kzL2Gp544XiUvPbVKtOA/txLi9Q==
|
integrity sha512-rru86D9CpQRLvsFG5XFdy0KdLAvjdQDyZCsRcuu60WtzFylDM3eAWSxEVz5kzL2Gp544XiUvPbVKtOA/txLi9Q==
|
||||||
|
|
||||||
|
tiny-warning@^1.0.2:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
|
||||||
|
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
|
||||||
|
|
||||||
tmp@^0.0.33:
|
tmp@^0.0.33:
|
||||||
version "0.0.33"
|
version "0.0.33"
|
||||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||||
|
@ -12212,11 +12172,6 @@ typo-js@*:
|
||||||
resolved "https://registry.yarnpkg.com/typo-js/-/typo-js-1.0.3.tgz#54d8ebc7949f1a7810908b6002c6841526c99d5a"
|
resolved "https://registry.yarnpkg.com/typo-js/-/typo-js-1.0.3.tgz#54d8ebc7949f1a7810908b6002c6841526c99d5a"
|
||||||
integrity sha1-VNjrx5SfGngQkItgAsaEFSbJnVo=
|
integrity sha1-VNjrx5SfGngQkItgAsaEFSbJnVo=
|
||||||
|
|
||||||
ua-parser-js@^0.7.18:
|
|
||||||
version "0.7.19"
|
|
||||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b"
|
|
||||||
integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==
|
|
||||||
|
|
||||||
uglify-js@3.4.x:
|
uglify-js@3.4.x:
|
||||||
version "3.4.10"
|
version "3.4.10"
|
||||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f"
|
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f"
|
||||||
|
@ -12949,11 +12904,6 @@ websocket-extensions@>=0.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29"
|
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29"
|
||||||
integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==
|
integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==
|
||||||
|
|
||||||
whatwg-fetch@>=0.10.0:
|
|
||||||
version "3.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
|
|
||||||
integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==
|
|
||||||
|
|
||||||
whet.extend@~0.9.9:
|
whet.extend@~0.9.9:
|
||||||
version "0.9.9"
|
version "0.9.9"
|
||||||
resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1"
|
resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1"
|
||||||
|
|
Loading…
Reference in a new issue