Compare commits

...

6 commits

Author SHA1 Message Date
zeppi
ecfbaea8b1 lbrysync demo initial - register / auth boilerplate 2022-07-12 11:43:49 -04:00
zeppi
16fe53c47d more odysee feature cleanup 2022-07-03 13:18:32 -04:00
zeppi
df6be2ac8f remove reward/verify components 2022-07-01 17:14:33 -04:00
zeppi
4c40b5a07f remove invite components 2022-07-01 17:14:16 -04:00
zeppi
2ca26c9332 prune some isAuthenticated 2022-07-01 17:14:15 -04:00
zeppi
0105f2516f rm modal youtube welcome 2022-07-01 17:14:14 -04:00
83 changed files with 316 additions and 2678 deletions

View file

@ -13,6 +13,7 @@ const config = {
LBRY_API_URL: process.env.LBRY_API_URL, //api.lbry.com', LBRY_API_URL: process.env.LBRY_API_URL, //api.lbry.com',
LBRY_WEB_STREAMING_API: process.env.LBRY_WEB_STREAMING_API, //player.odysee.com LBRY_WEB_STREAMING_API: process.env.LBRY_WEB_STREAMING_API, //player.odysee.com
LBRY_WEB_BUFFER_API: process.env.LBRY_WEB_BUFFER_API, LBRY_WEB_BUFFER_API: process.env.LBRY_WEB_BUFFER_API,
LBRYSYNC_API: process.env.LBRYSYNC_API,
SEARCH_SERVER_API: process.env.SEARCH_SERVER_API, SEARCH_SERVER_API: process.env.SEARCH_SERVER_API,
CLOUD_CONNECT_SITE_NAME: process.env.CLOUD_CONNECT_SITE_NAME, CLOUD_CONNECT_SITE_NAME: process.env.CLOUD_CONNECT_SITE_NAME,
COMMENT_SERVER_API: process.env.COMMENT_SERVER_API, COMMENT_SERVER_API: process.env.COMMENT_SERVER_API,

View file

@ -2314,5 +2314,6 @@
"Apply": "Apply", "Apply": "Apply",
"24-hour clock": "24-hour clock", "24-hour clock": "24-hour clock",
"Disable background": "Disable background", "Disable background": "Disable background",
"Odysee Connect --[Section in Help Page]--": "Odysee Connect",
"--end--": "--end--" "--end--": "--end--"
} }

View file

@ -7,7 +7,6 @@ import {
} from 'redux/selectors/claims'; } from 'redux/selectors/claims';
import { makeSelectPendingAmountByUri } from 'redux/selectors/wallet'; import { makeSelectPendingAmountByUri } from 'redux/selectors/wallet';
import { doOpenModal } from 'redux/actions/app'; import { doOpenModal } from 'redux/actions/app';
import { selectUser } from 'redux/selectors/user';
import FileDescription from './view'; import FileDescription from './view';
const select = (state, props) => { const select = (state, props) => {
@ -17,7 +16,6 @@ const select = (state, props) => {
claim, claim,
claimIsMine: selectClaimIsMine(state, claim), claimIsMine: selectClaimIsMine(state, claim),
metadata: makeSelectMetadataForUri(props.uri)(state), metadata: makeSelectMetadataForUri(props.uri)(state),
user: selectUser(state),
pendingAmount: makeSelectPendingAmountByUri(props.uri)(state), pendingAmount: makeSelectPendingAmountByUri(props.uri)(state),
tags: makeSelectTagsForUri(props.uri)(state), tags: makeSelectTagsForUri(props.uri)(state),
}; };

View file

@ -15,7 +15,6 @@ type Props = {
uri: string, uri: string,
claim: StreamClaim, claim: StreamClaim,
metadata: StreamMetadata, metadata: StreamMetadata,
user: ?any,
tags: any, tags: any,
pendingAmount: number, pendingAmount: number,
doOpenModal: (id: string, {}) => void, doOpenModal: (id: string, {}) => void,

View file

@ -7,7 +7,6 @@ import {
} from 'redux/selectors/claims'; } from 'redux/selectors/claims';
import { makeSelectPendingAmountByUri } from 'redux/selectors/wallet'; import { makeSelectPendingAmountByUri } from 'redux/selectors/wallet';
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info'; import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
import { selectUser } from 'redux/selectors/user';
import { doOpenModal } from 'redux/actions/app'; import { doOpenModal } from 'redux/actions/app';
import FileValues from './view'; import FileValues from './view';
@ -20,7 +19,6 @@ const select = (state, props) => {
contentType: makeSelectContentTypeForUri(props.uri)(state), contentType: makeSelectContentTypeForUri(props.uri)(state),
fileInfo: makeSelectFileInfoForUri(props.uri)(state), fileInfo: makeSelectFileInfoForUri(props.uri)(state),
metadata: makeSelectMetadataForUri(props.uri)(state), metadata: makeSelectMetadataForUri(props.uri)(state),
user: selectUser(state),
pendingAmount: makeSelectPendingAmountByUri(props.uri)(state), pendingAmount: makeSelectPendingAmountByUri(props.uri)(state),
claimIsMine: selectClaimIsMine(state, claim), claimIsMine: selectClaimIsMine(state, claim),
}; };

View file

@ -15,7 +15,6 @@ type Props = {
metadata: StreamMetadata, metadata: StreamMetadata,
openFolder: (string) => void, openFolder: (string) => void,
contentType: string, contentType: string,
user: ?any,
pendingAmount: string, pendingAmount: string,
openModal: (id: string, { uri: string }) => void, openModal: (id: string, { uri: string }) => void,
claimIsMine: boolean, claimIsMine: boolean,

View file

@ -6,12 +6,12 @@ import { selectClientSetting } from 'redux/selectors/settings';
import { selectGetSyncErrorMessage } from 'redux/selectors/sync'; import { selectGetSyncErrorMessage } from 'redux/selectors/sync';
import { selectHasNavigated } from 'redux/selectors/app'; import { selectHasNavigated } from 'redux/selectors/app';
import { selectTotalBalance, selectBalance } from 'redux/selectors/wallet'; import { selectTotalBalance, selectBalance } from 'redux/selectors/wallet';
import { selectUserVerifiedEmail, selectEmailToVerify, selectUser } from 'redux/selectors/user'; import { selectEmailToVerify, selectUser } from 'redux/selectors/user';
import { doLbrysyncRegister } from 'redux/actions/lbrysync';
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import Header from './view'; import Header from './view';
const select = (state) => ({ const select = (state) => ({
authenticated: selectUserVerifiedEmail(state),
balance: selectBalance(state), balance: selectBalance(state),
emailToVerify: selectEmailToVerify(state), emailToVerify: selectEmailToVerify(state),
hasNavigated: selectHasNavigated(state), hasNavigated: selectHasNavigated(state),
@ -25,6 +25,7 @@ const select = (state) => ({
const perform = (dispatch) => ({ const perform = (dispatch) => ({
clearEmailEntry: () => dispatch(doClearEmailEntry()), clearEmailEntry: () => dispatch(doClearEmailEntry()),
clearPasswordEntry: () => dispatch(doClearPasswordEntry()), clearPasswordEntry: () => dispatch(doClearPasswordEntry()),
lbrysyncRegister: (username, password) => dispatch(doLbrysyncRegister(username, password)),
signOut: () => dispatch(doSignOut()), signOut: () => dispatch(doSignOut()),
}); });

View file

@ -4,11 +4,10 @@ import { selectActiveChannelStakedLevel } from 'redux/selectors/app';
import { selectClientSetting } from 'redux/selectors/settings'; import { selectClientSetting } from 'redux/selectors/settings';
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import HeaderMenuButtons from './view'; import HeaderMenuButtons from './view';
import { selectUserVerifiedEmail, selectUser } from 'redux/selectors/user'; import { selectUser } from 'redux/selectors/user';
const select = (state) => ({ const select = (state) => ({
activeChannelStakedLevel: selectActiveChannelStakedLevel(state), activeChannelStakedLevel: selectActiveChannelStakedLevel(state),
authenticated: selectUserVerifiedEmail(state),
automaticDarkModeEnabled: selectClientSetting(state, SETTINGS.AUTOMATIC_DARK_MODE_ENABLED), automaticDarkModeEnabled: selectClientSetting(state, SETTINGS.AUTOMATIC_DARK_MODE_ENABLED),
currentTheme: selectClientSetting(state, SETTINGS.THEME), currentTheme: selectClientSetting(state, SETTINGS.THEME),
user: selectUser(state), user: selectUser(state),

View file

@ -12,7 +12,6 @@ import React from 'react';
import Tooltip from 'component/common/tooltip'; import Tooltip from 'component/common/tooltip';
type HeaderMenuButtonProps = { type HeaderMenuButtonProps = {
authenticated: boolean,
automaticDarkModeEnabled: boolean, automaticDarkModeEnabled: boolean,
currentTheme: string, currentTheme: string,
user: ?User, user: ?User,

View file

@ -1,14 +0,0 @@
import { connect } from 'react-redux';
import { selectReferralReward } from 'redux/selectors/rewards';
import { selectUserInvitees, selectUserInviteStatusIsPending } from 'redux/selectors/user';
import InviteList from './view';
const select = state => ({
invitees: selectUserInvitees(state),
isPending: selectUserInviteStatusIsPending(state),
referralReward: selectReferralReward(state),
});
const perform = () => ({});
export default connect(select, perform)(InviteList);

View file

@ -1,99 +0,0 @@
// @flow
import React from 'react';
import RewardLink from 'component/rewardLink';
import Icon from 'component/common/icon';
import * as ICONS from 'constants/icons';
import Card from 'component/common/card';
import LbcMessage from 'component/common/lbc-message';
type Props = {
invitees: ?Array<{
email: string,
invite_accepted: boolean,
invite_reward_claimed: boolean,
invite_reward_claimable: boolean,
}>,
referralReward: ?Reward,
};
class InviteList extends React.PureComponent<Props> {
render() {
const { invitees, referralReward } = this.props;
if (!invitees || !invitees.length) {
return null;
}
let rewardAmount = 0;
let rewardHelp = __(
"Woah, you have a lot of friends! You've claimed the maximum amount of invite rewards. Email %email% if you'd like to be whitelisted for more invites.",
{ email: 'hello@lbry.com' }
);
if (referralReward) {
rewardAmount = referralReward.reward_amount;
rewardHelp = referralReward.reward_description;
}
const showClaimable = invitees.some(invite => invite.invite_reward_claimable && !invite.invite_reward_claimed);
return (
<Card
title={<div className="table__header-text">{__('Invite History')}</div>}
subtitle={
<div className="table__header-text">
<LbcMessage>{rewardHelp}</LbcMessage>
</div>
}
titleActions={
referralReward &&
showClaimable && (
<div className="card__actions--inline">
<RewardLink
button
label={__(`Claim Your %reward_amount% Credit Invite Reward`, { reward_amount: rewardAmount })}
claim_code={referralReward.claim_code}
/>
</div>
)
}
isBodyList
body={
<div className="table__wrapper">
<table className="table section">
<thead>
<tr>
<th>{__('Invitee Email')}</th>
<th>{__('Invite Status')}</th>
<th>{__('Reward')}</th>
</tr>
</thead>
<tbody>
{invitees.map(invitee => (
<tr key={invitee.email}>
<td>{invitee.email}</td>
<td>
<span>{invitee.invite_accepted ? __('Accepted') : __('Not Accepted')}</span>
</td>
<td>
{invitee.invite_reward_claimed && (
<React.Fragment>
<span>{__('Claimed')}</span>
<Icon icon={ICONS.COMPLETE} />
</React.Fragment>
)}
{!invitee.invite_reward_claimed &&
(invitee.invite_reward_claimable ? <span>{__('Claimable')}</span> : __('Unclaimable'))}
</td>
</tr>
))}
</tbody>
</table>
</div>
}
/>
);
}
}
export default InviteList;

View file

@ -1,29 +0,0 @@
import { connect } from 'react-redux';
import {
selectUserInvitesRemaining,
selectUserInviteNewIsPending,
selectUserInviteNewErrorMessage,
selectUserInviteReferralLink,
selectUserInviteReferralCode,
} from 'redux/selectors/user';
// import { doUserInviteNew } from 'redux/actions/user';
import { selectMyChannelClaims, selectFetchingMyChannels } from 'redux/selectors/claims';
import { doFetchChannelListMine } from 'redux/actions/claims';
import InviteNew from './view';
const select = (state) => ({
errorMessage: selectUserInviteNewErrorMessage(state),
invitesRemaining: selectUserInvitesRemaining(state),
referralLink: selectUserInviteReferralLink(state),
referralCode: selectUserInviteReferralCode(state),
isPending: selectUserInviteNewIsPending(state),
channels: selectMyChannelClaims(state),
fetchingChannels: selectFetchingMyChannels(state),
});
const perform = (dispatch) => ({
// inviteNew: (email) => dispatch(doUserInviteNew(email)),
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
});
export default connect(select, perform)(InviteNew);

View file

@ -1,154 +0,0 @@
// @flow
import { URL, SITE_NAME } from 'config';
import React, { useEffect, useState } from 'react';
import Button from 'component/button';
import { Form, FormField } from 'component/common/form';
import CopyableText from 'component/copyableText';
import Card from 'component/common/card';
import analytics from 'analytics';
import I18nMessage from 'component/i18nMessage';
import LbcSymbol from 'component/common/lbc-symbol';
type Props = {
errorMessage: ?string,
inviteNew: (string) => void,
isPending: boolean,
referralLink: string,
referralCode: string,
channels: ?Array<ChannelClaim>,
};
function InviteNew(props: Props) {
const { inviteNew, errorMessage, isPending, referralCode = '', channels } = props;
// Email
const [email, setEmail] = useState('');
function handleSubmit() {
inviteNew(email);
}
function handleEmailChanged(event: any) {
setEmail(event.target.value);
}
// Referral link
const [referralSource, setReferralSource] = useState(referralCode);
const handleReferralChange = React.useCallback(
(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);
}
},
[setReferralSource]
);
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;
const referral = `${URL}/$/invite/${referralString.replace('#', ':')}`;
useEffect(() => {
// set default channel
if (topChannel) {
handleReferralChange(topChannel.name);
}
}, [topChannel, handleReferralChange]);
function lookupUrlByClaimName(name, channels) {
const claim = channels.find((channel) => channel.name === name);
return claim && claim.canonical_url ? claim.canonical_url.replace('lbry://', '') : name;
}
return (
<div className={'columns'}>
<div className="column">
<Card
title={__('Invites')}
subtitle={
<I18nMessage tokens={{ SITE_NAME, lbc: <LbcSymbol /> }}>
Earn %lbc% for inviting subscribers, followers, fans, friends, etc. to join and follow you on %SITE_NAME%.
You can use invites just like affiliate links.
</I18nMessage>
}
actions={
<React.Fragment>
<CopyableText label={__('Your invite link')} copyable={referral} />
{channels && channels.length > 0 && (
<FormField
type="select"
label={__('Customize link')}
value={referralSource}
onChange={(e) => handleReferralChange(e.target.value)}
>
{channels.map((channel) => (
<option key={channel.claim_id} value={channel.name}>
{channel.name}
</option>
))}
<option value={referralCode}>{referralCode}</option>
</FormField>
)}
</React.Fragment>
}
/>
</div>
<div className="column">
<Card
title={__('Invite by email')}
subtitle={
<I18nMessage tokens={{ SITE_NAME, lbc: <LbcSymbol /> }}>
Invite someone you know by email and earn %lbc% when they join %SITE_NAME%.
</I18nMessage>
}
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" />
),
}}
>
Read our %referral_faq_link% to learn more about rewards.
</I18nMessage>
</p>
</Form>
</React.Fragment>
}
/>
</div>
</div>
);
}
export default InviteNew;

View file

@ -1,30 +0,0 @@
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import REWARDS from 'rewards';
import { selectUser, selectSetReferrerPending, selectSetReferrerError } from 'redux/selectors/user';
import { doClaimRewardType } from 'redux/actions/rewards';
import { selectUnclaimedRewards } from 'redux/selectors/rewards';
import { doUserSetReferrer } from 'redux/actions/user';
import { selectIsSubscribedForUri } from 'redux/selectors/subscriptions';
import { doChannelSubscribe } from 'redux/actions/subscriptions';
import Invited from './view';
const select = (state, props) => {
return {
user: selectUser(state),
referrerSetPending: selectSetReferrerPending(state),
referrerSetError: selectSetReferrerError(state),
rewards: selectUnclaimedRewards(state),
isSubscribed: selectIsSubscribedForUri(state, props.fullUri),
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));

View file

@ -1,225 +0,0 @@
// @flow
import { SITE_NAME } from 'config';
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 { buildURI, parseURI } from 'util/lbryURI';
import { ERRORS } from 'lbryinc';
import REWARDS from 'rewards';
import { formatLbryUrlForWeb } from 'util/url';
import ChannelContent from 'component/channelContent';
import I18nMessage from 'component/i18nMessage';
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 {
isChannel: referrerIsChannel,
claimName: referrerChannelName,
channelClaimId: referrerChannelClaimId,
} = parseURI(refUri);
const channelUri =
referrerIsChannel &&
formatLbryUrlForWeb(buildURI({ channelName: referrerChannelName, channelClaimId: referrerChannelClaimId }));
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);
const redirect = channelUri || `/`;
// always follow if it's a channel
useEffect(() => {
if (fullUri && !isSubscribed && fullUri) {
let channelName;
try {
const { claimName } = parseURI(fullUri);
channelName = claimName;
} catch (e) {}
if (channelName) {
channelSubscribe({
channelName: channelName,
uri: fullUri,
});
}
}
}, [fullUri, isSubscribed, channelSubscribe]);
useEffect(() => {
if (!referrerSetPending && hasVerifiedEmail) {
claimReward();
}
}, [referrerSetPending, hasVerifiedEmail, claimReward]);
useEffect(() => {
if (referrer) {
setReferrer(referrer.replace(':', '#'));
}
}, [referrer, setReferrer]);
function handleDone() {
history.push(redirect);
}
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) {
const signUpButton = (
<Button
button="link"
label={hasVerifiedEmail ? __(`Finish verification `) : __(`Create an account `)}
navigate={`/$/${PAGES.AUTH}?redirect=/$/${PAGES.INVITE}/${referrer}`}
/>
);
return (
<Card
title={
referrerIsChannel
? __('%channel_name% invites you to the party!', { channel_name: referrerChannelName })
: __(`You're invited!`)
}
subtitle={
<p>
{referrerIsChannel ? (
<I18nMessage
tokens={{
channel_name: referrerChannelName,
signup_link: signUpButton,
SITE_NAME,
}}
>
%channel_name% is waiting for you on %SITE_NAME%. Create your account now.
</I18nMessage>
) : (
<I18nMessage
tokens={{
signup_link: signUpButton,
}}
>
Content freedom and a present are waiting for you. %signup_link% to claim it.
</I18nMessage>
)}
</p>
}
body={
referrerIsChannel && (
<div className="claim-preview--channel">
<div className="section">
<ClaimPreview key={refUri} uri={refUri} actions={''} type={'small'} />
</div>
<div className="section">
<ChannelContent uri={fullUri} defaultPageSize={3} defaultInfiniteScroll={false} />
</div>
</div>
)
}
actions={
<div className="section__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="section__actions">
<Button button="primary" label={__('Done')} onClick={handleDone} />
</div>
}
/>
);
}
export default Invited;

View file

@ -1,9 +0,0 @@
import { connect } from 'react-redux';
import { selectUnclaimedRewardValue } from 'redux/selectors/rewards';
import RewardAuthIntro from './view';
const select = state => ({
totalRewardValue: selectUnclaimedRewardValue(state),
});
export default connect(select, null)(RewardAuthIntro);

View file

@ -1,46 +0,0 @@
// @flow
import { SITE_NAME } from 'config';
import * as PAGES from 'constants/pages';
import React from 'react';
import CreditAmount from 'component/common/credit-amount';
import Button from 'component/button';
import Card from 'component/common/card';
import I18nMessage from 'component/i18nMessage';
type Props = {
balance: number,
totalRewardValue: number,
title?: string,
};
function RewardAuthIntro(props: Props) {
const { totalRewardValue, title } = props;
const totalRewardRounded = Math.floor(totalRewardValue / 10) * 10;
return (
<Card
title={title || __('Log in to %SITE_NAME% to earn rewards', { SITE_NAME })}
subtitle={
<I18nMessage
tokens={{
credit_amount: <CreditAmount inheritStyle amount={totalRewardRounded} />,
site_name: SITE_NAME,
}}
>
A %site_name% account allows you to earn more than %credit_amount% in rewards, backup your data, and get
content and security updates.
</I18nMessage>
}
actions={
<Button
requiresAuth
button="primary"
navigate={`/$/${PAGES.REWARDS_VERIFY}?redirect=/$/${PAGES.REWARDS}`}
label={__('Unlock Rewards')}
/>
}
/>
);
}
export default RewardAuthIntro;

View file

@ -1,17 +0,0 @@
import { connect } from 'react-redux';
import { makeSelectRewardByClaimCode, makeSelectIsRewardClaimPending } from 'redux/selectors/rewards';
import { doClaimRewardType } from 'redux/actions/rewards';
import RewardLink from './view';
const select = (state, props) => ({
isPending: makeSelectIsRewardClaimPending()(state, props),
reward: makeSelectRewardByClaimCode()(state, props.claim_code),
});
const perform = dispatch => ({
claimReward: reward =>
dispatch(doClaimRewardType(reward.reward_type, { notifyError: true, params: { claim_code: reward.claim_code } })),
});
export default connect(select, perform)(RewardLink);

View file

@ -1,48 +0,0 @@
// @flow
import React from 'react';
import Button from 'component/button';
import LbcMessage from 'component/common/lbc-message';
type Reward = {
reward_amount: number,
reward_range: string,
};
type Props = {
isPending: boolean,
label: ?string,
reward: Reward,
button: ?boolean,
disabled: boolean,
claimReward: (Reward) => void,
};
const RewardLink = (props: Props) => {
const { reward, claimReward, label, isPending, button, disabled = false } = props;
let displayLabel = label;
if (isPending) {
displayLabel = __('Claiming...');
} else if (label) {
displayLabel = label;
} else if (reward && reward.reward_range && reward.reward_range.includes('-')) {
displayLabel = __('Claim %range% LBC', { range: reward.reward_range });
} else if (reward && reward.reward_amount > 0) {
displayLabel = __('Claim %amount% LBC', { amount: reward.reward_amount });
} else {
displayLabel = __('Claim ??? LBC');
}
return !reward ? null : (
<Button
button={button ? 'primary' : 'link'}
disabled={disabled || isPending}
label={<LbcMessage>{displayLabel}</LbcMessage>}
aria-label={displayLabel}
onClick={() => {
claimReward(reward);
}}
/>
);
};
export default RewardLink;

View file

@ -1,9 +0,0 @@
import { connect } from 'react-redux';
import { selectClaimedRewards } from 'redux/selectors/rewards';
import RewardListClaimed from './view';
const select = state => ({
rewards: selectClaimedRewards(state),
});
export default connect(select, null)(RewardListClaimed);

View file

@ -1,68 +0,0 @@
// @flow
import React from 'react';
import ButtonTransaction from 'component/common/transaction-link';
import moment from 'moment';
import LbcSymbol from 'component/common/lbc-symbol';
import Card from 'component/common/card';
type Reward = {
id: string,
reward_title: string,
reward_amount: number,
transaction_id: string,
created_at: string,
};
type Props = {
rewards: Array<Reward>,
};
const RewardListClaimed = (props: Props) => {
const { rewards } = props;
if (!rewards || !rewards.length) {
return null;
}
return (
<Card
title={<div className="table__header-text">{__('Claimed Rewards')}</div>}
subtitle={
<div className="table__header-text">
{__(
'Reward history is tied to your email. In case of lost or multiple wallets, your balance may differ from the amounts claimed'
)}
</div>
}
isBodyList
body={
<div className="table__wrapper">
<table className="table table--rewards">
<thead>
<tr>
<th>{__('Title')}</th>
<th>
<LbcSymbol size={20} />
</th>
<th>{__('Transaction')}</th>
<th>{__('Date')}</th>
</tr>
</thead>
<tbody>
{rewards.reverse().map(reward => (
<tr key={reward.id}>
<td>{reward.reward_title}</td>
<td>{reward.reward_amount}</td>
<td>{reward.transaction_id && <ButtonTransaction id={reward.transaction_id} />}</td>
<td>{moment(reward.created_at).format('LLL')}</td>
</tr>
))}
</tbody>
</table>
</div>
}
/>
);
};
export default RewardListClaimed;

View file

@ -1,10 +0,0 @@
import { connect } from 'react-redux';
import { selectUnclaimedRewardValue, selectFetchingRewards } from 'redux/selectors/rewards';
import RewardSummary from './view';
const select = state => ({
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
fetching: selectFetchingRewards(state),
});
export default connect(select, null)(RewardSummary);

View file

@ -1,52 +0,0 @@
// @flow
import * as React from 'react';
import Button from 'component/button';
import CreditAmount from 'component/common/credit-amount';
import I18nMessage from 'component/i18nMessage';
import Card from 'component/common/card';
type Props = {
unclaimedRewardAmount: number,
fetching: boolean,
};
class RewardSummary extends React.Component<Props> {
render() {
const { unclaimedRewardAmount, fetching } = this.props;
const hasRewards = unclaimedRewardAmount > 0;
return (
<Card
title={__('Available rewards')}
subtitle={
<React.Fragment>
{fetching && __('You have...')}
{!fetching && hasRewards ? (
<I18nMessage
tokens={{
credit_amount: <CreditAmount inheritStyle amount={unclaimedRewardAmount} precision={8} />,
}}
f
>
You have %credit_amount% in unclaimed rewards.
</I18nMessage>
) : (
__('You have no rewards available.')
)}
</React.Fragment>
}
actions={
<React.Fragment>
<Button
button="primary"
navigate="/$/rewards"
label={hasRewards ? __('Claim Rewards') : __('View Rewards')}
/>
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/rewards" />
</React.Fragment>
}
/>
);
}
}
export default RewardSummary;

View file

@ -1,15 +0,0 @@
import * as MODALS from 'constants/modal_types';
import { connect } from 'react-redux';
import { doOpenModal } from 'redux/actions/app';
import { selectUser } from 'redux/selectors/user';
import RewardTile from './view';
const select = state => ({
user: selectUser(state),
});
const perform = dispatch => ({
openRewardCodeModal: () => dispatch(doOpenModal(MODALS.REWARD_GENERATED_CODE)),
openSetReferrerModal: () => dispatch(doOpenModal(MODALS.SET_REFERRER)),
});
export default connect(select, perform)(RewardTile);

View file

@ -1,78 +0,0 @@
// @flow
import * as ICONS from 'constants/icons';
import React from 'react';
import Icon from 'component/common/icon';
import RewardLink from 'component/rewardLink';
import Button from 'component/button';
import Card from 'component/common/card';
import rewards from 'rewards';
import LbcMessage from 'component/common/lbc-message';
type Props = {
openRewardCodeModal: () => void,
openSetReferrerModal: () => void,
reward: {
id: string,
reward_title: string,
reward_amount: number,
reward_range?: string,
transaction_id: string,
created_at: string,
reward_description: string,
reward_type: string,
claim_code: string,
},
user: User,
disabled: boolean,
};
const RewardTile = (props: Props) => {
const { reward, openRewardCodeModal, openSetReferrerModal, user, disabled = false } = props;
const referrerSet = user && user.invited_by_id;
const claimed = !!reward.transaction_id;
const customActionsRewards = [rewards.TYPE_REFERRAL, rewards.TYPE_REFEREE];
return (
<Card
title={__(reward.reward_title)}
subtitle={<LbcMessage>{reward.reward_description}</LbcMessage>}
actions={
<div className="section__actions">
{reward.reward_type === rewards.TYPE_GENERATED_CODE && (
<Button button="primary" onClick={openRewardCodeModal} label={__('Enter Code')} disabled={disabled} />
)}
{reward.reward_type === rewards.TYPE_REFERRAL && (
<Button
button="primary"
navigate="/$/invite"
label={__('Go To Invites')}
aria-hidden={disabled}
tabIndex={disabled ? -1 : 0}
/>
)}
{reward.reward_type === rewards.TYPE_REFEREE && (
<>
{referrerSet && <RewardLink button reward_type={reward.reward_type} disabled={disabled} />}
<Button
button={referrerSet ? 'link' : 'primary'}
onClick={openSetReferrerModal}
label={referrerSet ? __('Change Inviter') : __('Set Inviter')}
disabled={disabled}
/>
</>
)}
{!customActionsRewards.some((i) => i === reward.reward_type) &&
(claimed ? (
<span>
<Icon icon={ICONS.COMPLETED} /> {__('Reward claimed.')}
</span>
) : (
<RewardLink button claim_code={reward.claim_code} disabled={disabled} />
))}
</div>
}
/>
);
};
export default RewardTile;

View file

@ -1,16 +0,0 @@
import { connect } from 'react-redux';
import { selectUnclaimedRewardValue, selectFetchingRewards, selectClaimedRewards } from 'redux/selectors/rewards';
import { doRewardList } from 'redux/actions/rewards';
import RewardSummary from './view';
const select = state => ({
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
fetching: selectFetchingRewards(state),
rewards: selectClaimedRewards(state),
});
const perform = dispatch => ({
fetchRewards: () => dispatch(doRewardList()),
});
export default connect(select, perform)(RewardSummary);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View file

@ -1,26 +0,0 @@
// @flow
import React from 'react';
import TotalBackground from './total-background.png';
import useTween from 'effects/use-tween';
import I18nMessage from 'component/i18nMessage';
import LbcSymbol from 'component/common/lbc-symbol';
type Props = {
rewards: Array<Reward>,
};
function RewardTotal(props: Props) {
const { rewards } = props;
const rewardTotal = rewards.reduce((acc, val) => acc + val.reward_amount, 0);
const modifier = rewardTotal > 500 ? 1 : 15; // used to tweak the reward count speed
const total = useTween(rewardTotal * modifier);
const integer = Math.round(total * rewardTotal);
return (
<section className="card card--section card--reward-total" style={{ backgroundImage: `url(${TotalBackground})` }}>
<I18nMessage tokens={{ amount: integer, lbc: <LbcSymbol /> }}>%amount% %lbc% earned from rewards</I18nMessage>
</section>
);
}
export default RewardTotal;

View file

@ -37,7 +37,6 @@ import ChannelsPage from 'page/channels';
import CreatorDashboard from 'page/creatorDashboard'; import CreatorDashboard from 'page/creatorDashboard';
import DiscoverPage from 'page/discover'; import DiscoverPage from 'page/discover';
import FileListPublished from 'page/fileListPublished'; import FileListPublished from 'page/fileListPublished';
import FourOhFourPage from 'page/fourOhFour';
import HelpPage from 'page/help'; import HelpPage from 'page/help';
import LibraryPage from 'page/library'; import LibraryPage from 'page/library';
import ListBlockedPage from 'page/listBlocked'; import ListBlockedPage from 'page/listBlocked';
@ -227,18 +226,20 @@ function AppRouter(props: Props) {
/> />
))} ))}
{/* Odysee signin */}
<Route path={`/$/${PAGES.AUTH_SIGNIN}`} exact component={SignInPage} /> <Route path={`/$/${PAGES.AUTH_SIGNIN}`} exact component={SignInPage} />
<Route path={`/$/${PAGES.AUTH_PASSWORD_RESET}`} exact component={PasswordResetPage} /> <Route path={`/$/${PAGES.AUTH_PASSWORD_RESET}`} exact component={PasswordResetPage} />
<Route path={`/$/${PAGES.AUTH_PASSWORD_SET}`} exact component={PasswordSetPage} /> <Route path={`/$/${PAGES.AUTH_PASSWORD_SET}`} exact component={PasswordSetPage} />
<Route path={`/$/${PAGES.AUTH}`} exact component={SignUpPage} /> <Route path={`/$/${PAGES.AUTH}`} exact component={SignUpPage} />
<Route path={`/$/${PAGES.AUTH}/*`} exact component={SignUpPage} /> <Route path={`/$/${PAGES.AUTH}/*`} exact component={SignUpPage} />
<Route path={`/$/${PAGES.AUTH_VERIFY}`} exact component={SignInVerifyPage} />
<Route path={`/$/${PAGES.WELCOME}`} exact component={Welcome} /> <Route path={`/$/${PAGES.WELCOME}`} exact component={Welcome} />
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} /> <Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
{/* @if TARGET='app' */} {/* @if TARGET='app' */}
<Route path={`/$/${PAGES.BACKUP}`} exact component={BackupPage} /> <Route path={`/$/${PAGES.BACKUP}`} exact component={BackupPage} />
{/* @endif */} {/* @endif */}
<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.TOP}`} exact component={TopPage} /> <Route path={`/$/${PAGES.TOP}`} exact component={TopPage} />
<Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} /> <Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} />
@ -279,7 +280,6 @@ function AppRouter(props: Props) {
{/* Below need to go at the end to make sure we don't match any of our pages first */} {/* Below need to go at the end to make sure we don't match any of our pages first */}
<Route path="/:claimName" exact component={ShowPage} /> <Route path="/:claimName" exact component={ShowPage} />
<Route path="/:claimName/:streamName" exact component={ShowPage} /> <Route path="/:claimName/:streamName" exact component={ShowPage} />
<Route path="/*" component={FourOhFourPage} />
</Switch> </Switch>
); );
} }

View file

@ -53,9 +53,7 @@ export default function SettingAccount(props: Props) {
</SettingsRow> </SettingsRow>
)} )}
{/* @if TARGET='app' */}
<SyncToggle disabled={walletEncrypted && !storedPassword && storedPassword !== ''} /> <SyncToggle disabled={walletEncrypted && !storedPassword && storedPassword !== ''} />
{/* @endif */}
{hasChannels && ( {hasChannels && (
<SettingsRow title={__('Comments')} subtitle={__('View your past comments.')}> <SettingsRow title={__('Comments')} subtitle={__('View your past comments.')}>

View file

@ -11,7 +11,7 @@ import {
import { doSetDaemonSetting, doClearDaemonSetting, doFindFFmpeg } from 'redux/actions/settings'; import { doSetDaemonSetting, doClearDaemonSetting, doFindFFmpeg } from 'redux/actions/settings';
import { selectAllowAnalytics } from 'redux/selectors/app'; import { selectAllowAnalytics } from 'redux/selectors/app';
import { selectDaemonSettings, selectFfmpegStatus, selectFindingFFmpeg } from 'redux/selectors/settings'; import { selectDaemonSettings, selectFfmpegStatus, selectFindingFFmpeg } from 'redux/selectors/settings';
import { selectUserVerifiedEmail } from 'redux/selectors/user'; import { selectUserVerifiedEmail } from 'redux/selectors/user'; // here
import SettingSystem from './view'; import SettingSystem from './view';
@ -20,7 +20,7 @@ const select = (state) => ({
ffmpegStatus: selectFfmpegStatus(state), ffmpegStatus: selectFfmpegStatus(state),
findingFFmpeg: selectFindingFFmpeg(state), findingFFmpeg: selectFindingFFmpeg(state),
walletEncrypted: selectWalletIsEncrypted(state), walletEncrypted: selectWalletIsEncrypted(state),
isAuthenticated: selectUserVerifiedEmail(state), isAuthenticated: selectUserVerifiedEmail(state), // odysee
allowAnalytics: selectAllowAnalytics(state), allowAnalytics: selectAllowAnalytics(state),
}); });

View file

@ -6,15 +6,13 @@ import * as KEYCODES from 'constants/keycodes';
import React from 'react'; import React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import classnames from 'classnames'; import classnames from 'classnames';
import Icon from 'component/common/icon';
import NotificationBubble from 'component/notificationBubble'; import NotificationBubble from 'component/notificationBubble';
import DebouncedInput from 'component/common/debounced-input'; import DebouncedInput from 'component/common/debounced-input';
import I18nMessage from 'component/i18nMessage';
import ChannelThumbnail from 'component/channelThumbnail'; import ChannelThumbnail from 'component/channelThumbnail';
import { useIsMobile, isTouch } from 'effects/use-screensize'; import { useIsMobile, isTouch } from 'effects/use-screensize';
import { IS_MAC } from 'component/app/view'; import { IS_MAC } from 'component/app/view';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { DOMAIN, ENABLE_UI_NOTIFICATIONS } from 'config'; import { ENABLE_UI_NOTIFICATIONS } from 'config';
const FOLLOWED_ITEM_INITIAL_LIMIT = 10; const FOLLOWED_ITEM_INITIAL_LIMIT = 10;
const touch = isTouch(); const touch = isTouch();
@ -184,7 +182,6 @@ function SideNavigation(props: Props) {
]; ];
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui); const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui);
const isAuthenticated = Boolean(email);
const [pulseLibrary, setPulseLibrary] = React.useState(false); const [pulseLibrary, setPulseLibrary] = React.useState(false);
const [expandSubscriptions, setExpandSubscriptions] = React.useState(false); const [expandSubscriptions, setExpandSubscriptions] = React.useState(false);
@ -357,23 +354,6 @@ function SideNavigation(props: Props) {
return () => window.removeEventListener('keydown', handleKeydown); return () => window.removeEventListener('keydown', handleKeydown);
}, [sidebarOpen, setSidebarOpen, isAbsolute]); }, [sidebarOpen, setSidebarOpen, isAbsolute]);
const unAuthNudge =
DOMAIN === 'lbry.tv' ? null : (
<div className="navigation__auth-nudge">
<span>
<I18nMessage tokens={{ lbc: <Icon icon={ICONS.LBC} /> }}>
Sign up to earn %lbc% for you and your favorite creators.
</I18nMessage>
</span>
<Button
button="secondary"
label={__('Sign Up')}
navigate={`/$/${PAGES.AUTH}?src=sidenav_nudge`}
disabled={user === null}
/>{' '}
</div>
);
const helpLinks = ( const helpLinks = (
<ul className="navigation__tertiary navigation-links--small"> <ul className="navigation__tertiary navigation-links--small">
<li className="navigation-link"> <li className="navigation-link">
@ -437,7 +417,6 @@ function SideNavigation(props: Props) {
{getSubscriptionSection()} {getSubscriptionSection()}
{getFollowedTagsSection()} {getFollowedTagsSection()}
{!isAuthenticated && sidebarOpen && unAuthNudge}
</div> </div>
)} )}
{(!canDisposeMenu || sidebarOpen) && shouldRenderLargeMenu && helpLinks} {(!canDisposeMenu || sidebarOpen) && shouldRenderLargeMenu && helpLinks}

View file

@ -1,14 +0,0 @@
import { connect } from 'react-redux';
import { selectPhoneNewErrorMessage } from 'redux/selectors/user';
import { doUserPhoneNew } from 'redux/actions/user';
import UserPhoneNew from './view';
const select = state => ({
phoneErrorMessage: selectPhoneNewErrorMessage(state),
});
const perform = dispatch => ({
addUserPhone: (phone, countryCode) => dispatch(doUserPhoneNew(phone, countryCode)),
});
export default connect(select, perform)(UserPhoneNew);

View file

@ -1,124 +0,0 @@
// @flow
import * as React from 'react';
import { Form, FormField, Submit } from 'component/common/form';
import Card from 'component/common/card';
const os = require('os').type();
const countryCodes = require('country-data')
.callingCountries.all.filter(_ => _.emoji)
.reduce((acc, cur) => acc.concat(cur.countryCallingCodes.map(_ => ({ ...cur, countryCallingCode: _ }))), [])
.sort((a, b) => {
if (a.countryCallingCode < b.countryCallingCode) {
return -1;
}
if (a.countryCallingCode > b.countryCallingCode) {
return 1;
}
return 0;
});
type Props = {
addUserPhone: (string, string) => void,
cancelButton: React.Node,
phoneErrorMessage: ?string,
isPending: boolean,
};
type State = {
phone: string,
countryCode: string,
};
class UserPhoneNew extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
phone: '',
countryCode: '+1',
};
(this: any).formatPhone = this.formatPhone.bind(this);
(this: any).handleSubmit = this.handleSubmit.bind(this);
(this: any).handleSelect = this.handleSelect.bind(this);
}
formatPhone(value: string) {
const { countryCode } = this.state;
const formattedNumber = value.replace(/\D/g, '');
if (countryCode === '+1') {
if (!formattedNumber) {
return '';
} else if (formattedNumber.length < 4) {
return formattedNumber;
} else if (formattedNumber.length < 7) {
return `(${formattedNumber.substring(0, 3)}) ${formattedNumber.substring(3)}`;
}
const fullNumber = `(${formattedNumber.substring(0, 3)}) ${formattedNumber.substring(
3,
6
)}-${formattedNumber.substring(6)}`;
return fullNumber.length <= 14 ? fullNumber : fullNumber.substring(0, 14);
}
return formattedNumber;
}
handleChanged(event: SyntheticInputEvent<*>) {
this.setState({
phone: this.formatPhone(event.target.value),
});
}
handleSelect(event: SyntheticInputEvent<*>) {
this.setState({ countryCode: event.target.value });
}
handleSubmit() {
const { phone, countryCode } = this.state;
this.props.addUserPhone(phone.replace(/\D/g, ''), countryCode.substring(1));
}
render() {
const { cancelButton, phoneErrorMessage, isPending } = this.props;
return (
<Card
title={__('Enter your phone number')}
subtitle={__(
'Enter your phone number and we will send you a verification code. We will not share your phone number with third parties.'
)}
actions={
<Form onSubmit={this.handleSubmit}>
<fieldset-group class="fieldset-group--smushed">
<FormField label={__('Country')} type="select" name="country-codes" onChange={this.handleSelect}>
{countryCodes.map((country, index) => (
<option key={index} value={country.countryCallingCode}>
{os === 'Darwin' ? country.emoji : `(${country.alpha2})`} {country.countryCallingCode}
</option>
))}
</FormField>
<FormField
type="text"
label={__('Number')}
placeholder={this.state.countryCode === '+1' ? '(555) 555-5555' : '5555555555'}
name="phone"
value={this.state.phone}
error={phoneErrorMessage}
onChange={event => {
this.handleChanged(event);
}}
/>
</fieldset-group>
<div className="card__actions">
<Submit label="Submit" disabled={isPending} />
{cancelButton}
</div>
</Form>
}
/>
);
}
}
export default UserPhoneNew;

View file

@ -1,17 +0,0 @@
import { connect } from 'react-redux';
import { doUserPhoneVerify, doUserPhoneReset } from 'redux/actions/user';
import { selectPhoneToVerify, selectPhoneVerifyErrorMessage, selectUserCountryCode } from 'redux/selectors/user';
import UserPhoneVerify from './view';
const select = state => ({
phone: selectPhoneToVerify(state),
countryCode: selectUserCountryCode(state),
phoneErrorMessage: selectPhoneVerifyErrorMessage(state),
});
const perform = dispatch => ({
resetPhone: () => dispatch(doUserPhoneReset()),
verifyUserPhone: code => dispatch(doUserPhoneVerify(code)),
});
export default connect(select, perform)(UserPhoneVerify);

View file

@ -1,90 +0,0 @@
// @flow
import * as React from 'react';
import Button from 'component/button';
import { Form, FormField, Submit } from 'component/common/form';
import I18nMessage from 'component/i18nMessage';
import Card from 'component/common/card';
import { SITE_HELP_EMAIL } from 'config';
type Props = {
verifyUserPhone: (string) => void,
resetPhone: () => void,
phoneErrorMessage: string,
phone: string,
countryCode: string,
};
type State = {
code: string,
};
class UserPhoneVerify extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
code: '',
};
}
handleCodeChanged(event: SyntheticInputEvent<*>) {
this.setState({
code: String(event.target.value).trim(),
});
}
handleSubmit() {
const { code } = this.state;
this.props.verifyUserPhone(code);
}
reset() {
const { resetPhone } = this.props;
resetPhone();
}
render() {
const { phoneErrorMessage, phone, countryCode } = this.props;
return (
<Card
title={__('Enter the verification code')}
subtitle={
<>
{__(`Please enter the verification code sent to +${countryCode}${phone}. Didn't receive it? `)}
<Button button="link" onClick={this.reset.bind(this)} label={__('Go back.')} />
</>
}
actions={
<>
<Form onSubmit={this.handleSubmit.bind(this)}>
<FormField
type="text"
name="code"
placeholder="1234"
value={this.state.code}
onChange={(event) => {
this.handleCodeChanged(event);
}}
label={__('Verification Code')}
error={phoneErrorMessage}
inputButton={<Submit label={__('Verify')} />}
/>
</Form>
<p className="help">
<I18nMessage
tokens={{
help_link: <Button button="link" href={`mailto:${SITE_HELP_EMAIL}`} label={`${SITE_HELP_EMAIL}`} />,
chat_link: <Button button="link" href="https://chat.lbry.com" label={__('chat')} />,
}}
>
Email %help_link% or join our %chat_link% if you encounter any trouble with your code.
</I18nMessage>
</p>
</>
}
/>
);
}
}
export default UserPhoneVerify;

View file

@ -1,26 +0,0 @@
import * as MODALS from 'constants/modal_types';
import { connect } from 'react-redux';
import { doOpenModal } from 'redux/actions/app';
import { doUserIdentityVerify, doUserFetch } from 'redux/actions/user';
import { makeSelectRewardByType } from 'redux/selectors/rewards';
import rewards from 'rewards';
import { selectIdentityVerifyIsPending, selectIdentityVerifyErrorMessage } from 'redux/selectors/user';
import UserVerify from './view';
const select = state => {
const selectReward = makeSelectRewardByType();
return {
isPending: selectIdentityVerifyIsPending(state),
errorMessage: selectIdentityVerifyErrorMessage(state),
reward: selectReward(state, rewards.TYPE_NEW_USER),
};
};
const perform = dispatch => ({
verifyUserIdentity: token => dispatch(doUserIdentityVerify(token)),
verifyPhone: () => dispatch(doOpenModal(MODALS.PHONE_COLLECTION)),
fetchUser: () => dispatch(doUserFetch()),
});
export default connect(select, perform)(UserVerify);

View file

@ -1,164 +0,0 @@
// @flow
import { SITE_NAME } from 'config';
import * as ICONS from 'constants/icons';
import React, { Fragment } from 'react';
import Button from 'component/button';
import CardVerify from 'component/cardVerify';
import { Lbryio } from 'lbryinc';
import Card from 'component/common/card';
import I18nMessage from 'component/i18nMessage';
import LbcSymbol from 'component/common/lbc-symbol';
type Props = {
errorMessage: ?string,
isPending: boolean,
verifyUserIdentity: (string) => void,
verifyPhone: () => void,
fetchUser: () => void,
skipLink?: string,
onSkip: () => void,
};
class UserVerify extends React.PureComponent<Props> {
constructor() {
super();
(this: any).onToken = this.onToken.bind(this);
}
onToken(data: { id: string }) {
this.props.verifyUserIdentity(data.id);
}
render() {
const { errorMessage, isPending, verifyPhone, fetchUser, onSkip } = this.props;
const skipButtonProps = {
onClick: onSkip,
};
return (
<div className="main__auth-content">
<section className="section__header">
<h1 className="section__title--large">
{''}
<I18nMessage
tokens={{
lbc: <LbcSymbol size={48} />,
}}
>
Verify to earn %lbc%
</I18nMessage>
</h1>
<p>
<I18nMessage
tokens={{
rewards_program: (
<Button button="link" label={__('other rewards')} href="https://lbry.com/faq/rewards" />
),
Refresh: <Button onClick={() => fetchUser()} button="link" label={__('Refresh')} />,
Skip: <Button {...skipButtonProps} button="link" label={__('Skip')} />,
SITE_NAME,
}}
>
Verified accounts are eligible to earn LBRY Credits for views, watching and reposting content, sharing
invite links etc. Verifying also helps us keep the %SITE_NAME% community safe too! %Refresh% or %Skip%.
</I18nMessage>
</p>
<p className="help">
{__('This step is not mandatory and not required in order for you to use %SITE_NAME%.', { SITE_NAME })}
</p>
</section>
<div className="section">
<Card
icon={ICONS.PHONE}
title={__('Verify phone number')}
subtitle={__(
'You will receive an SMS text message confirming your phone number is valid. May not be available in all regions.'
)}
actions={
<Fragment>
<Button
onClick={() => {
verifyPhone();
}}
button="primary"
label={__('Verify Via Text')}
/>
<p className="help">
{__('Standard messaging rates apply. Having trouble?')}{' '}
<Button button="link" href="https://lbry.com/faq/phone" label={__('Read more')} />.
</p>
</Fragment>
}
/>
<div className="section__divider">
<hr />
<p>{__('OR')}</p>
</div>
<Card
icon={ICONS.WALLET}
title={__('Verify via credit card')}
subtitle={__('Your card information will not be stored or charged, now or in the future.')}
actions={
<Fragment>
{errorMessage && <p className="error__text">{errorMessage}</p>}
<CardVerify
label={__('Verify Card')}
disabled={isPending}
token={this.onToken}
stripeKey={Lbryio.getStripeToken()}
/>
<p className="help">
{__('A $1 authorization may temporarily appear with your provider.')}{' '}
<Button button="link" href="https://lbry.com/faq/identity-requirements" label={__('Read more')} />.
</p>
</Fragment>
}
/>
<div className="section__divider">
<hr />
<p>{__('OR')}</p>
</div>
<Card
icon={ICONS.CHAT}
title={__('Verify via chat')}
subtitle={
<>
<p>
{__(
'A moderator can approve you within our discord server. Please review the instructions within #rewards-approval carefully.'
)}
</p>
<p>{__('You will be asked to provide proof of identity.')}</p>
</>
}
actions={<Button href="https://chat.lbry.com" button="primary" label={__('Join LBRY Chat')} />}
/>
<div className="section__divider">
<hr />
<p>{__('OR')}</p>
</div>
<Card
icon={ICONS.REMOVE}
title={__('Skip')}
subtitle={__("Verifying is optional. If you skip this, it just means you can't earn LBRY Credits.")}
actions={
<Fragment>
<Button {...skipButtonProps} button="primary" label={__('Continue Without Verifying')} />
</Fragment>
}
/>
</div>
</div>
);
}
}
export default UserVerify;

View file

@ -43,7 +43,6 @@ type Props = {
toggleVideoTheaterMode: () => void, toggleVideoTheaterMode: () => void,
toggleAutoplayNext: () => void, toggleAutoplayNext: () => void,
setVideoPlaybackRate: (number) => void, setVideoPlaybackRate: (number) => void,
authenticated: boolean,
userId: number, userId: number,
homepageData?: { [string]: HomepageCat }, homepageData?: { [string]: HomepageCat },
shareTelemetry: boolean, shareTelemetry: boolean,

View file

@ -3,14 +3,12 @@ import { doSetDaemonSetting } from 'redux/actions/settings';
import { doSetWelcomeVersion } from 'redux/actions/app'; import { doSetWelcomeVersion } from 'redux/actions/app';
import * as DAEMON_SETTINGS from 'constants/daemon_settings'; import * as DAEMON_SETTINGS from 'constants/daemon_settings';
import { WELCOME_VERSION } from 'config.js'; import { WELCOME_VERSION } from 'config.js';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectDaemonSettings, selectDaemonStatus } from 'redux/selectors/settings'; import { selectDaemonSettings, selectDaemonStatus } from 'redux/selectors/settings';
import WelcomeSplash from './view'; import WelcomeSplash from './view';
import { selectDiskSpace } from 'redux/selectors/app'; import { selectDiskSpace } from 'redux/selectors/app';
const select = (state) => ({ const select = (state) => ({
authenticated: selectUserVerifiedEmail(state),
diskSpace: selectDiskSpace(state), diskSpace: selectDiskSpace(state),
daemonSettings: selectDaemonSettings(state), daemonSettings: selectDaemonSettings(state),
daemonStatus: selectDaemonStatus(state), daemonStatus: selectDaemonStatus(state),

View file

@ -9,7 +9,6 @@ import YrblHappy from 'static/img/yrblhappy.svg';
type Props = { type Props = {
setWelcomeVersion: (number) => void, setWelcomeVersion: (number) => void,
setShareDataInternal: (boolean) => void, setShareDataInternal: (boolean) => void,
authenticated: boolean,
handleNextPage: () => void, handleNextPage: () => void,
diskSpace?: DiskSpace, diskSpace?: DiskSpace,
}; };

View file

@ -497,3 +497,11 @@ export const UPDATE_UPLOAD_PROGRESS = 'UPDATE_UPLOAD_PROGRESS';
export const GENERATE_AUTH_TOKEN_FAILURE = 'GENERATE_AUTH_TOKEN_FAILURE'; export const GENERATE_AUTH_TOKEN_FAILURE = 'GENERATE_AUTH_TOKEN_FAILURE';
export const GENERATE_AUTH_TOKEN_STARTED = 'GENERATE_AUTH_TOKEN_STARTED'; export const GENERATE_AUTH_TOKEN_STARTED = 'GENERATE_AUTH_TOKEN_STARTED';
export const GENERATE_AUTH_TOKEN_SUCCESS = 'GENERATE_AUTH_TOKEN_SUCCESS'; export const GENERATE_AUTH_TOKEN_SUCCESS = 'GENERATE_AUTH_TOKEN_SUCCESS';
// Lbry Sync
export const LSYNC_REGISTER_STARTED = 'LSYNC_REGISTER_STARTED';
export const LSYNC_REGISTER_COMPLETED = 'LSYNC_REGISTER_COMPLETED'; // created account
export const LSYNC_REGISTER_FAILED = 'LSYNC_REGISTER_FAILED';
export const LSYNC_AUTH_STARTED = 'LSYNC_AUTH_STARTED';
export const LSYNC_AUTH_COMPLETED = 'LSYNC_AUTH_COMPLETED'; // got token
export const LSYNC_AUTH_FAILED = 'LSYNC_AUTH_FAILED';

View file

@ -33,12 +33,9 @@ export const WALLET_UNLOCK = 'wallet_unlock';
export const WALLET_SYNC = 'wallet_sync'; export const WALLET_SYNC = 'wallet_sync';
export const WALLET_PASSWORD_UNSAVE = 'wallet_password_unsave'; export const WALLET_PASSWORD_UNSAVE = 'wallet_password_unsave';
export const CREATE_CHANNEL = 'create_channel'; export const CREATE_CHANNEL = 'create_channel';
export const YOUTUBE_WELCOME = 'youtube_welcome';
export const SET_REFERRER = 'set_referrer';
export const SIGN_OUT = 'sign_out'; export const SIGN_OUT = 'sign_out';
export const LIQUIDATE_SUPPORTS = 'liquidate_supports'; export const LIQUIDATE_SUPPORTS = 'liquidate_supports';
export const MASS_TIP_UNLOCK = 'mass_tip_unlock'; export const MASS_TIP_UNLOCK = 'mass_tip_unlock';
export const CONFIRM_AGE = 'confirm_age';
export const SYNC_ENABLE = 'SYNC_ENABLE'; export const SYNC_ENABLE = 'SYNC_ENABLE';
export const IMAGE_UPLOAD = 'image_upload'; export const IMAGE_UPLOAD = 'image_upload';
export const MOBILE_SEARCH = 'mobile_search'; export const MOBILE_SEARCH = 'mobile_search';

83
ui/lbrysync.js Normal file
View file

@ -0,0 +1,83 @@
// @flow
/*
DeriveSecrets
POST /
*/
import { LBRYSYNC_API as BASE_URL } from 'config';
const SYNC_API_DOWN = 'sync_api_down';
const DUPLICATE_EMAIL = 'duplicate_email';
const UNKNOWN_ERROR = 'unknown_api_error';
const API_VERSION = 2;
// const API_URL = `${BASE_URL}/api/${API_VERSION}`;
const AUTH_ENDPOINT = '/auth/full';
const REGISTER_ENDPOINT = '/signup';
// const WALLET_ENDPOINT = '/wallet';
const Lbrysync = {
apiRequestHeaders: { 'Content-Type': 'application/json' },
apiUrl: `${BASE_URL}/api/${API_VERSION}`,
setApiHeader: (key: string, value: string) => {
Lbrysync.apiRequestHeaders = Object.assign(Lbrysync.apiRequestHeaders, { [key]: value });
},
// store state "registered email: email"
register: async (email: string, password: string) => {
try {
const result = await callWithResult(REGISTER_ENDPOINT, { email, password });
return result;
} catch (e) {
return e.message;
}
},
// store state "lbrysynctoken: token"
getAuthToken: async (email: string, password: string, deviceId: string) => {
try {
const result = await callWithResult(AUTH_ENDPOINT, { email, password, deviceId });
return { token: result };
} catch (e) {
return { error: e.message };
}
},
};
function callWithResult(endpoint: string, params: ?{} = {}) {
return new Promise((resolve, reject) => {
apiCall(
endpoint,
params,
(result) => {
resolve(result);
},
reject
);
});
}
function apiCall(endpoint: string, params: ?{}, resolve: Function, reject: Function) {
const options = {
method: 'POST',
body: JSON.stringify(params),
};
return fetch(`${Lbrysync.apiUrl}${endpoint}`, options)
.then(handleResponse)
.then((response) => {
return resolve(response.result);
})
.catch(reject);
}
function handleResponse(response) {
if (response.status >= 200 && response.status < 300) {
return Promise.resolve(response.json());
}
if (response.status === 500) {
return Promise.reject(SYNC_API_DOWN);
}
if (response.status === 409) {
return Promise.reject(DUPLICATE_EMAIL);
}
return Promise.reject(UNKNOWN_ERROR);
}
export default Lbrysync;

View file

@ -1,9 +0,0 @@
import { connect } from 'react-redux';
import { doHideModal } from 'redux/actions/app';
import { doSetClientSetting } from 'redux/actions/settings';
import ModalAffirmPurchase from './view';
export default connect(null, {
doHideModal,
doSetClientSetting,
})(ModalAffirmPurchase);

View file

@ -1,52 +0,0 @@
// @flow
import * as SETTINGS from 'constants/settings';
import React from 'react';
import { Modal } from 'modal/modal';
import Card from 'component/common/card';
import Button from 'component/button';
import { FormField } from 'component/common/form';
type Props = {
doHideModal: () => void,
doSetClientSetting: (string, any) => void,
};
function ModalAffirmPurchase(props: Props) {
const { doHideModal, doSetClientSetting } = props;
const [confirmed, setConfirmed] = React.useState(false);
function handleConfirmAge() {
doSetClientSetting(SETTINGS.SHOW_MATURE, true);
doHideModal();
}
const title = __('Confirm Your Age');
return (
<Modal type="card" isOpen contentLabel={title} onAborted={doHideModal}>
<Card
title={title}
actions={
<>
<div className="section">
<FormField
name="age-confirmation"
type="checkbox"
label={__('I confirm I am over 18 years old.')}
helper={__('This is only for regulatory compliance and the data will not be stored.')}
checked={confirmed}
onChange={() => setConfirmed(!confirmed)}
/>
</div>
<div className="section__actions">
<Button button="primary" label={'Confirm'} onClick={handleConfirmAge} disabled={!confirmed} />
<Button button="link" label={__('Cancel')} onClick={doHideModal} />
</div>
</>
}
/>
</Modal>
);
}
export default ModalAffirmPurchase;

View file

@ -1,15 +0,0 @@
import { connect } from 'react-redux';
import { doHideModal } from 'redux/actions/app';
import { selectPhoneToVerify, selectUser } from 'redux/selectors/user';
import ModalPhoneCollection from './view';
const select = state => ({
phone: selectPhoneToVerify(state),
user: selectUser(state),
});
const perform = dispatch => () => ({
closeModal: () => dispatch(doHideModal()),
});
export default connect(select, perform)(ModalPhoneCollection);

View file

@ -1,50 +0,0 @@
// @flow
import React from 'react';
import { Modal } from 'modal/modal';
import Button from 'component/button';
import UserPhoneVerify from 'component/userPhoneVerify';
import UserPhoneNew from 'component/userPhoneNew';
import { Redirect } from 'react-router';
type Props = {
phone: ?number,
user: {
is_identity_verified: boolean,
},
closeModal: () => void,
history: { push: string => void },
};
class ModalPhoneCollection extends React.PureComponent<Props> {
renderInner() {
const { closeModal, phone, user } = this.props;
const cancelButton = <Button button="link" onClick={closeModal} label={__('Not Now')} />;
if (!user.is_identity_verified && !phone) {
return <UserPhoneNew cancelButton={cancelButton} />;
} else if (!user.is_identity_verified) {
return <UserPhoneVerify cancelButton={cancelButton} />;
}
closeModal();
return <Redirect to="/$/rewards" />;
}
render() {
const { user, closeModal } = this.props;
// this shouldn't happen
if (!user) {
return null;
}
return (
<Modal type="card" isOpen contentLabel="Phone" onAborted={closeModal}>
{this.renderInner()}
</Modal>
);
}
}
export default ModalPhoneCollection;

View file

@ -15,7 +15,6 @@ import ModalClaimCollectionAdd from 'modal/modalClaimCollectionAdd';
import ModalCommentAcknowledgement from 'modal/modalCommentAcknowledgement'; import ModalCommentAcknowledgement from 'modal/modalCommentAcknowledgement';
import ModalConfirmAge from 'modal/modalConfirmAge';
import ModalConfirmThumbnailUpload from 'modal/modalConfirmThumbnailUpload'; import ModalConfirmThumbnailUpload from 'modal/modalConfirmThumbnailUpload';
import ModalConfirmTransaction from 'modal/modalConfirmTransaction'; import ModalConfirmTransaction from 'modal/modalConfirmTransaction';
@ -39,8 +38,6 @@ import ModalOpenExternalResource from 'modal/modalOpenExternalResource';
import ModalPasswordUnsave from 'modal/modalPasswordUnsave'; import ModalPasswordUnsave from 'modal/modalPasswordUnsave';
import ModalPhoneCollection from 'modal/modalPhoneCollection';
import ModalPublish from 'modal/modalPublish'; import ModalPublish from 'modal/modalPublish';
import ModalPublishPreview from 'modal/modalPublishPreview'; import ModalPublishPreview from 'modal/modalPublishPreview';
@ -54,7 +51,6 @@ import ModalRevokeClaim from 'modal/modalRevokeClaim';
import ModalRewardCode from 'modal/modalRewardCode'; import ModalRewardCode from 'modal/modalRewardCode';
import ModalSendTip from 'modal/modalSendTip'; import ModalSendTip from 'modal/modalSendTip';
import ModalRepost from 'modal/modalRepost'; import ModalRepost from 'modal/modalRepost';
import ModalSetReferrer from 'modal/modalSetReferrer';
import ModalSignOut from 'modal/modalSignOut'; import ModalSignOut from 'modal/modalSignOut';
import ModalSocialShare from 'modal/modalSocialShare'; import ModalSocialShare from 'modal/modalSocialShare';
import ModalSupportsLiquidate from 'modal/modalSupportsLiquidate'; import ModalSupportsLiquidate from 'modal/modalSupportsLiquidate';
@ -70,8 +66,6 @@ import ModalWalletEncrypt from 'modal/modalWalletEncrypt';
import ModalWalletUnlock from 'modal/modalWalletUnlock'; import ModalWalletUnlock from 'modal/modalWalletUnlock';
import ModalYoutubeWelcome from 'modal/modalYoutubeWelcome';
function getModal(id) { function getModal(id) {
switch (id) { switch (id) {
case MODALS.UPGRADE: case MODALS.UPGRADE:
@ -96,8 +90,7 @@ function getModal(id) {
return ModalAffirmPurchase; return ModalAffirmPurchase;
case MODALS.CONFIRM_CLAIM_REVOKE: case MODALS.CONFIRM_CLAIM_REVOKE:
return ModalRevokeClaim; return ModalRevokeClaim;
case MODALS.PHONE_COLLECTION:
return ModalPhoneCollection;
case MODALS.FIRST_SUBSCRIPTION: case MODALS.FIRST_SUBSCRIPTION:
return ModalFirstSubscription; return ModalFirstSubscription;
case MODALS.SEND_TIP: case MODALS.SEND_TIP:
@ -128,14 +121,8 @@ function getModal(id) {
return ModalRewardCode; return ModalRewardCode;
case MODALS.COMMENT_ACKNOWEDGEMENT: case MODALS.COMMENT_ACKNOWEDGEMENT:
return ModalCommentAcknowledgement; return ModalCommentAcknowledgement;
case MODALS.YOUTUBE_WELCOME:
return ModalYoutubeWelcome;
case MODALS.SET_REFERRER:
return ModalSetReferrer;
case MODALS.SIGN_OUT: case MODALS.SIGN_OUT:
return ModalSignOut; return ModalSignOut;
case MODALS.CONFIRM_AGE:
return ModalConfirmAge;
case MODALS.FILE_SELECTION: case MODALS.FILE_SELECTION:
return ModalFileSelection; return ModalFileSelection;
case MODALS.LIQUIDATE_SUPPORTS: case MODALS.LIQUIDATE_SUPPORTS:

View file

@ -1,18 +0,0 @@
import { connect } from 'react-redux';
import { doHideModal } from 'redux/actions/app';
import { selectSetReferrerError, selectSetReferrerPending } from 'redux/selectors/user';
import { doUserSetReferrer, doUserSetReferrerReset } from 'redux/actions/user';
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)),
resetReferrerError: () => dispatch(doUserSetReferrerReset()),
});
export default connect(select, perform)(ModalSetReferrer);

View file

@ -1,101 +0,0 @@
// @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';
import Card from 'component/common/card';
type Props = {
closeModal: () => void,
error: ?string,
rewardIsPending: boolean,
setReferrer: (string, boolean) => void,
referrerSetPending: boolean,
referrerSetError?: string,
resetReferrerError: () => void,
};
type State = {
referrer: string,
};
class ModalSetReferrer extends React.PureComponent<Props, State> {
constructor() {
super();
this.state = {
referrer: '',
};
(this: any).handleSubmit = this.handleSubmit.bind(this);
(this: any).handleClose = this.handleClose.bind(this);
(this: any).handleTextChange = this.handleTextChange.bind(this);
}
handleSubmit() {
const { referrer } = this.state;
const { setReferrer } = this.props;
setReferrer(referrer, true);
}
handleClose() {
const { referrerSetError, resetReferrerError, closeModal } = this.props;
if (referrerSetError) {
resetReferrerError();
}
closeModal();
}
handleTextChange(e: SyntheticInputEvent<*>) {
const { referrerSetError, resetReferrerError } = this.props;
this.setState({ referrer: e.target.value });
if (referrerSetError) {
resetReferrerError();
}
}
render() {
const { closeModal, rewardIsPending, referrerSetError, referrerSetPending } = this.props;
const { referrer } = this.state;
return (
<Modal isOpen contentLabel={__('Enter inviter')} type="card" onAborted={closeModal}>
<Card
title={__('Enter inviter')}
subtitle={
<React.Fragment>
{__('Did someone invite you to use LBRY? Tell us who and you both get a reward!')}
<HelpLink href="https://lbry.com/faq/referrals" />
</React.Fragment>
}
actions={
<React.Fragment>
<Form onSubmit={this.handleSubmit}>
<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={this.handleTextChange}
error={!referrerSetPending && referrerSetError}
/>
</Form>
<div className="card__actions">
<Button button="primary" label={__('Done')} onClick={this.handleClose} />
</div>
</React.Fragment>
}
/>
</Modal>
);
}
}
export default ModalSetReferrer;

View file

@ -1,14 +0,0 @@
import { connect } from 'react-redux';
import { doHideModal } from 'redux/actions/app';
import WalletSend from './view';
const select = state => ({});
const perform = {
doHideModal,
};
export default connect(
select,
perform
)(WalletSend);

View file

@ -1,47 +0,0 @@
// @flow
import * as PAGES from 'constants/pages';
import React from 'react';
import { Modal } from 'modal/modal';
import Card from 'component/common/card';
import Confetti from 'react-confetti';
import Button from 'component/button';
type Props = { doHideModal: () => void };
const YoutubeWelcome = (props: Props) => {
const { doHideModal } = props;
return (
<Modal isOpen type="card" onAborted={doHideModal}>
<Confetti recycle={false} style={{ position: 'fixed' }} numberOfPieces={100} />
<Card
title={__("You're free!")}
subtitle={
<React.Fragment>
<p>
{__("You've escaped the land of spying, censorship, and exploitation.")}
<span className="emoji"> 💩</span>
</p>
<p>
{__('Welcome to the land of content freedom.')}
<span className="emoji"> 🌈</span>
</p>
</React.Fragment>
}
actions={
<div className="card__actions">
<Button
button="primary"
label={__('Create an Account')}
navigate={`/$/${PAGES.AUTH}`}
onClick={doHideModal}
/>
<Button button="link" label={__('Not Yet')} onClick={doHideModal} />
</div>
}
/>
</Modal>
);
};
export default YoutubeWelcome;

View file

@ -3,7 +3,6 @@ import { connect } from 'react-redux';
import { doResolveUri } from 'redux/actions/claims'; import { doResolveUri } from 'redux/actions/claims';
import { selectClaimForUri } from 'redux/selectors/claims'; import { selectClaimForUri } from 'redux/selectors/claims';
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectFollowedTags } from 'redux/selectors/tags'; import { selectFollowedTags } from 'redux/selectors/tags';
import { doToggleTagFollowDesktop } from 'redux/actions/tags'; import { doToggleTagFollowDesktop } from 'redux/actions/tags';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
@ -18,7 +17,6 @@ const select = (state, props) => {
followedTags: selectFollowedTags(state), followedTags: selectFollowedTags(state),
repostedUri: repostedUri, repostedUri: repostedUri,
repostedClaim: repostedUri ? selectClaimForUri(state, repostedUri) : null, repostedClaim: repostedUri ? selectClaimForUri(state, repostedUri) : null,
isAuthenticated: selectUserVerifiedEmail(state),
tileLayout: makeSelectClientSetting(SETTINGS.TILE_LAYOUT)(state), tileLayout: makeSelectClientSetting(SETTINGS.TILE_LAYOUT)(state),
}; };
}; };

View file

@ -24,7 +24,6 @@ type Props = {
repostedClaim: ?GenericClaim, repostedClaim: ?GenericClaim,
doToggleTagFollowDesktop: (string) => void, doToggleTagFollowDesktop: (string) => void,
doResolveUri: (string) => void, doResolveUri: (string) => void,
isAuthenticated: boolean,
dynamicRouteProps: RowDataItem, dynamicRouteProps: RowDataItem,
tileLayout: boolean, tileLayout: boolean,
}; };

View file

@ -1,9 +0,0 @@
import { connect } from 'react-redux';
import FourOhFourPage from './view';
const select = state => ({});
export default connect(
select,
null
)(FourOhFourPage);

View file

@ -1,16 +0,0 @@
import React from 'react';
import Page from 'component/page';
import Yrbl from 'component/yrbl';
const FourOhFourPage = () => (
<Page notcontained>
<div className="main main--empty">
<Yrbl type="sad" title={__('404')} subtitle={<p>{__('Page Not Found')}</p>} />
<p>{__('Four-Oh-Four-Oh-Four-Oh-Four! Four-Oh-Four!')}</p>
<p>{__('Four-Oh-Four, Oh, Four...')}</p>
<p>{__('Four-Oh-Four-Oh-Four-Oh-Four, Oh-Four, Oh-Four...')}</p>
</div>
</Page>
);
export default FourOhFourPage;

View file

@ -183,7 +183,6 @@ class HelpPage extends React.PureComponent<Props, State> {
} }
/> />
{/* @if TARGET='app' */}
<Card <Card
title={__('View your log')} title={__('View your log')}
subtitle={ subtitle={
@ -214,8 +213,58 @@ class HelpPage extends React.PureComponent<Props, State> {
/> />
<WalletBackup /> <WalletBackup />
{/* @endif */} <Card
<> title={__('Odysee Connect --[Section in Help Page]--')}
isBodyList
body={
<div className="table__wrapper">
<table className="table table--stretch">
<tbody>
<tr>
<td>{__('Connected Email')}</td>
<td>
{user && user.primary_email ? (
<React.Fragment>
{user.primary_email}{' '}
<Button
button="link"
navigate={`/$/${PAGES.SETTINGS_NOTIFICATIONS}`}
label={__('Update mailing preferences')}
/>
</React.Fragment>
) : (
<React.Fragment>
<span className="empty">{__('none')} </span>
<Button button="link" onClick={() => doAuth()} label={__('set email')} />
</React.Fragment>
)}
</td>
</tr>
<tr>
<td>{__('Reward Eligible')}</td>
<td>{user && user.is_reward_approved ? __('Yes') : __('No')}</td>
</tr>
<tr>
<td>{__('Access Token')}</td>
<td>
{this.state.accessTokenHidden && (
<Button button="link" label={__('View')} onClick={this.showAccessToken} />
)}
{!this.state.accessTokenHidden && accessToken && (
<div>
<p>{accessToken}</p>
<div className="help--warning">
{__('This is equivalent to a password. Do not post or share this.')}
</div>
</div>
)}
</td>
</tr>
</tbody>
</table>
</div>
}
/>
<Card <Card
title={__('About --[About section in Help Page]--')} title={__('About --[About section in Help Page]--')}
subtitle={ subtitle={
@ -246,30 +295,6 @@ class HelpPage extends React.PureComponent<Props, State> {
<td>{__('Daemon (lbrynet)')}</td> <td>{__('Daemon (lbrynet)')}</td>
<td>{ver ? ver.lbrynet_version : __('Loading...')}</td> <td>{ver ? ver.lbrynet_version : __('Loading...')}</td>
</tr> </tr>
<tr>
<td>{__('Connected Email')}</td>
<td>
{user && user.primary_email ? (
<React.Fragment>
{user.primary_email}{' '}
<Button
button="link"
navigate={`/$/${PAGES.SETTINGS_NOTIFICATIONS}`}
label={__('Update mailing preferences')}
/>
</React.Fragment>
) : (
<React.Fragment>
<span className="empty">{__('none')} </span>
<Button button="link" onClick={() => doAuth()} label={__('set email')} />
</React.Fragment>
)}
</td>
</tr>
<tr>
<td>{__('Reward Eligible')}</td>
<td>{user && user.is_reward_approved ? __('Yes') : __('No')}</td>
</tr>
<tr> <tr>
<td>{__('Platform')}</td> <td>{__('Platform')}</td>
<td>{platform}</td> <td>{platform}</td>
@ -278,28 +303,11 @@ class HelpPage extends React.PureComponent<Props, State> {
<td>{__('Installation ID')}</td> <td>{__('Installation ID')}</td>
<td>{this.state.lbryId}</td> <td>{this.state.lbryId}</td>
</tr> </tr>
<tr>
<td>{__('Access Token')}</td>
<td>
{this.state.accessTokenHidden && (
<Button button="link" label={__('View')} onClick={this.showAccessToken} />
)}
{!this.state.accessTokenHidden && accessToken && (
<div>
<p>{accessToken}</p>
<div className="help--warning">
{__('This is equivalent to a password. Do not post or share this.')}
</div>
</div>
)}
</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
} }
/> />
</>
</Page> </Page>
); );
} }

View file

@ -1,6 +1,5 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectFollowedTags } from 'redux/selectors/tags'; import { selectFollowedTags } from 'redux/selectors/tags';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectSubscriptions } from 'redux/selectors/subscriptions'; import { selectSubscriptions } from 'redux/selectors/subscriptions';
import { selectShowMatureContent, selectHomepageData } from 'redux/selectors/settings'; import { selectShowMatureContent, selectHomepageData } from 'redux/selectors/settings';
@ -9,7 +8,6 @@ import DiscoverPage from './view';
const select = (state) => ({ const select = (state) => ({
followedTags: selectFollowedTags(state), followedTags: selectFollowedTags(state),
subscribedChannels: selectSubscriptions(state), subscribedChannels: selectSubscriptions(state),
authenticated: selectUserVerifiedEmail(state),
showNsfw: selectShowMatureContent(state), showNsfw: selectShowMatureContent(state),
homepageData: selectHomepageData(state), homepageData: selectHomepageData(state),
}); });

View file

@ -13,7 +13,6 @@ import { useIsLargeScreen } from 'effects/use-screensize';
import { GetLinksData } from 'util/buildHomepage'; import { GetLinksData } from 'util/buildHomepage';
type Props = { type Props = {
authenticated: boolean,
followedTags: Array<Tag>, followedTags: Array<Tag>,
subscribedChannels: Array<Subscription>, subscribedChannels: Array<Subscription>,
showNsfw: boolean, showNsfw: boolean,
@ -21,7 +20,7 @@ type Props = {
}; };
function HomePage(props: Props) { function HomePage(props: Props) {
const { followedTags, subscribedChannels, authenticated, showNsfw, homepageData } = props; const { followedTags, subscribedChannels, showNsfw, homepageData } = props;
const showPersonalizedChannels = subscribedChannels && subscribedChannels.length > 0; const showPersonalizedChannels = subscribedChannels && subscribedChannels.length > 0;
const showPersonalizedTags = followedTags && followedTags.length > 0; const showPersonalizedTags = followedTags && followedTags.length > 0;
const showIndividualTags = showPersonalizedTags && followedTags.length < 5; const showIndividualTags = showPersonalizedTags && followedTags.length < 5;
@ -31,7 +30,6 @@ function HomePage(props: Props) {
homepageData, homepageData,
isLargeScreen, isLargeScreen,
true, true,
authenticated,
showPersonalizedChannels, showPersonalizedChannels,
showPersonalizedTags, showPersonalizedTags,
subscribedChannels, subscribedChannels,

View file

@ -1,25 +0,0 @@
import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux';
import {
selectUserInviteStatusFailed,
selectUserInviteStatusIsPending,
selectUserVerifiedEmail,
} from 'redux/selectors/user';
// import { doFetchInviteStatus } from 'redux/actions/user';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { doSetClientSetting } from 'redux/actions/settings';
import InvitePage from './view';
const select = (state) => ({
isFailed: selectUserInviteStatusFailed(state),
isPending: selectUserInviteStatusIsPending(state),
inviteAcknowledged: makeSelectClientSetting(SETTINGS.INVITE_ACKNOWLEDGED)(state),
authenticated: selectUserVerifiedEmail(state),
});
const perform = (dispatch) => ({
// fetchInviteStatus: () => dispatch(doFetchInviteStatus()),
acknowledgeInivte: () => dispatch(doSetClientSetting(SETTINGS.INVITE_ACKNOWLEDGED, true)),
});
export default connect(select, perform)(InvitePage);

View file

@ -1,55 +0,0 @@
// @flow
import { SITE_NAME } from 'config';
import React from 'react';
import BusyIndicator from 'component/common/busy-indicator';
import InviteNew from 'component/inviteNew';
import InviteList from 'component/inviteList';
import Page from 'component/page';
import RewardAuthIntro from 'component/rewardAuthIntro';
type Props = {
isPending: boolean,
isFailed: boolean,
inviteAcknowledged: boolean,
authenticated: boolean,
acknowledgeInivte: () => void,
fetchInviteStatus: () => void,
};
class InvitePage extends React.PureComponent<Props> {
componentDidMount() {
const { fetchInviteStatus, inviteAcknowledged, acknowledgeInivte } = this.props;
fetchInviteStatus();
if (!inviteAcknowledged) {
acknowledgeInivte();
}
}
render() {
const { isPending, isFailed, authenticated } = this.props;
return (
<Page>
{!authenticated ? (
<RewardAuthIntro
title={__('Log in to %SITE_NAME% to earn rewards From Inviting Your Friends', { SITE_NAME })}
/>
) : (
<React.Fragment>
{isPending && <BusyIndicator message={__('Checking your invite status')} />}
{!isPending && isFailed && <span className="empty">{__('Failed to retrieve invite status.')}</span>}
{!isPending && !isFailed && (
<React.Fragment>
<InviteNew />
<InviteList />
</React.Fragment>
)}
</React.Fragment>
)}
</Page>
);
}
}
export default InvitePage;

View file

@ -1,19 +0,0 @@
import { connect } from 'react-redux';
import InvitedPage from './view';
import { makeSelectPermanentUrlForUri } from 'redux/selectors/claims';
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));

View file

@ -1,18 +0,0 @@
// @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>
<Invited fullUri={fullUri} referrer={referrer} />
</Page>
);
}

View file

@ -1,22 +0,0 @@
import { connect } from 'react-redux';
import { selectUser } from 'redux/selectors/user';
import { selectFetchingRewards, selectUnclaimedRewards, selectClaimedRewards } from 'redux/selectors/rewards';
import { doUserFetch } from 'redux/actions/user';
import { doRewardList } from 'redux/actions/rewards';
import { selectDaemonSettings } from 'redux/selectors/settings';
import RewardsPage from './view';
const select = state => ({
daemonSettings: selectDaemonSettings(state),
fetching: selectFetchingRewards(state),
rewards: selectUnclaimedRewards(state),
claimed: selectClaimedRewards(state),
user: selectUser(state),
});
const perform = dispatch => ({
fetchRewards: () => dispatch(doRewardList()),
fetchUser: () => dispatch(doUserFetch()),
});
export default connect(select, perform)(RewardsPage);

View file

@ -1,185 +0,0 @@
// @flow
import * as PAGES from 'constants/pages';
import React, { PureComponent } from 'react';
import BusyIndicator from 'component/common/busy-indicator';
import RewardListClaimed from 'component/rewardListClaimed';
import RewardTile from 'component/rewardTile';
import Button from 'component/button';
import Page from 'component/page';
import classnames from 'classnames';
import REWARD_TYPES from 'rewards';
import RewardAuthIntro from 'component/rewardAuthIntro';
import Card from 'component/common/card';
import I18nMessage from 'component/i18nMessage';
import { SITE_HELP_EMAIL, SITE_NAME } from 'config';
type Props = {
doAuth: () => void,
fetchRewards: () => void,
fetchUser: () => void,
fetching: boolean,
rewards: Array<Reward>,
claimed: Array<Reward>,
user: ?{
is_identity_verified: boolean,
is_reward_approved: boolean,
primary_email: string,
has_verified_email: boolean,
},
daemonSettings: {
share_usage_data: boolean,
},
};
class RewardsPage extends PureComponent<Props> {
componentDidMount() {
const { user, fetchUser, fetchRewards } = this.props;
const rewardsApproved = user && user.is_reward_approved;
fetchRewards();
if (!rewardsApproved) {
fetchUser();
}
}
renderPageHeader() {
const { user, daemonSettings, fetchUser } = this.props;
const rewardsEnabled = daemonSettings && daemonSettings.share_usage_data;
if (user && !user.is_reward_approved && rewardsEnabled) {
if (!user.primary_email || !user.has_verified_email || !user.is_identity_verified) {
return (
<div className="section">
<RewardAuthIntro />
</div>
);
}
return (
<Card
className="section"
title={__('Reward validation pending')}
body={
<React.Fragment>
<p>
{__(
'This account must undergo review before you can participate in the rewards program. Not all users and regions may qualify.'
)}{' '}
{__('This can take anywhere from a few hours to several days. Please be patient.')}
</p>
<p>
{__(
'We apologize for this inconvenience, but have added this additional step to prevent abuse. Users on VPN or shared connections will continue to see this message and are not eligible for Rewards.'
)}
</p>
<p>
<I18nMessage
tokens={{
rewards_faq: <Button button="link" label={__('Rewards FAQ')} href="https://lbry.com/faq/support" />,
help_email: SITE_HELP_EMAIL,
site_name: SITE_NAME,
}}
>
Please review the %rewards_faq% for eligibility, and send us an email to %help_email% if you continue
to see this message. You can continue to use %site_name% without this feature.
</I18nMessage>
{`${__('Enjoy all the awesome free content in the meantime!')}`}
</p>
</React.Fragment>
}
actions={
<div className="section__actions">
<Button navigate="/" button="primary" label="Return Home" />
<Button onClick={() => fetchUser()} button="link" label="Refresh" />
</div>
}
/>
);
}
return null;
}
renderCustomRewardCode() {
const { user } = this.props;
const isNotEligible = !user || !user.primary_email || !user.has_verified_email || !user.is_reward_approved;
return (
<RewardTile
key={REWARD_TYPES.TYPE_GENERATED_CODE}
reward={{
reward_type: REWARD_TYPES.TYPE_GENERATED_CODE,
reward_title: __('Custom Code'),
reward_description: __('Are you a supermodel or rockstar that received a custom reward code? Claim it here.'),
}}
disabled={isNotEligible}
/>
);
}
renderUnclaimedRewards() {
const { fetching, rewards, user, daemonSettings, claimed } = this.props;
if (daemonSettings && !daemonSettings.share_usage_data) {
return (
<section className="card card--section">
<h2 className="card__title card__title--deprecated">{__('Rewards Disabled')}</h2>
<p className="error__text">
<I18nMessage tokens={{ settings: <Button button="link" navigate="/$/settings" label="Settings" /> }}>
Rewards are currently disabled for your account. Turn on diagnostic data sharing, in %settings%, to
re-enable them.
</I18nMessage>
</p>
</section>
);
} else if (fetching) {
return <BusyIndicator message={__('Fetching rewards')} />;
} else if (user === null) {
return (
<p className="help">{__('This application is unable to earn rewards due to an authentication failure.')}</p>
);
} else if (!rewards || rewards.length <= 0) {
return (
<Card
title={__('No rewards available')}
subtitle={
claimed && claimed.length
? __(
"You have claimed all available rewards! We're regularly adding more so be sure to check back later."
)
: __('There are no rewards available at this time, please check back later.')
}
actions={<Button button="primary" navigate={`/$/${PAGES.DISCOVER}`} label={__('Go Home')} />}
/>
);
}
const isNotEligible = !user || !user.primary_email || !user.has_verified_email || !user.is_reward_approved;
return (
<div
aria-hidden={isNotEligible}
className={classnames('card__list', {
'card--disabled': isNotEligible,
})}
>
{rewards.map((reward) => (
<RewardTile disabled={isNotEligible} key={reward.claim_code} reward={reward} />
))}
{this.renderCustomRewardCode()}
</div>
);
}
render() {
return (
<Page>
{this.renderPageHeader()}
<div className="section">{this.renderUnclaimedRewards()}</div>
<RewardListClaimed />
</Page>
);
}
}
export default RewardsPage;

View file

@ -1,9 +0,0 @@
import { connect } from 'react-redux';
import { selectUser } from 'redux/selectors/user';
import RewardsVerifyPage from './view';
const select = state => ({
user: selectUser(state),
});
export default connect(select, null)(RewardsVerifyPage);

View file

@ -1,29 +0,0 @@
// @flow
import React from 'react';
import UserVerify from 'component/userVerify';
import Page from 'component/page';
import { useHistory } from 'react-router-dom';
type Props = {
user: ?User,
};
function RewardsVerifyPage(props: Props) {
const { user } = props;
const { goBack } = useHistory();
const rewardsApproved = user && user.is_reward_approved;
React.useEffect(() => {
if (rewardsApproved) {
goBack();
}
}, [rewardsApproved]);
return (
<Page>
<UserVerify onSkip={() => goBack()} />
</Page>
);
}
export default RewardsVerifyPage;

View file

@ -8,7 +8,6 @@ import {
makeSelectHasReachedMaxResultsLength, makeSelectHasReachedMaxResultsLength,
} from 'redux/selectors/search'; } from 'redux/selectors/search';
import { selectShowMatureContent } from 'redux/selectors/settings'; import { selectShowMatureContent } from 'redux/selectors/settings';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { getSearchQueryString } from 'util/query-params'; import { getSearchQueryString } from 'util/query-params';
import SearchPage from './view'; import SearchPage from './view';
@ -36,7 +35,6 @@ const select = (state, props) => {
searchOptions, searchOptions,
isSearching: selectIsSearching(state), isSearching: selectIsSearching(state),
uris: uris, uris: uris,
isAuthenticated: selectUserVerifiedEmail(state),
hasReachedMaxResultsLength: hasReachedMaxResultsLength, hasReachedMaxResultsLength: hasReachedMaxResultsLength,
}; };
}; };

View file

@ -16,7 +16,6 @@ type Props = {
search: (string, SearchOptions) => void, search: (string, SearchOptions) => void,
isSearching: boolean, isSearching: boolean,
uris: Array<string>, uris: Array<string>,
isAuthenticated: boolean,
hasReachedMaxResultsLength: boolean, hasReachedMaxResultsLength: boolean,
}; };

View file

@ -1,13 +1,11 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doEnterSettingsPage, doExitSettingsPage } from 'redux/actions/settings'; import { doEnterSettingsPage, doExitSettingsPage } from 'redux/actions/settings';
import { selectDaemonSettings } from 'redux/selectors/settings'; import { selectDaemonSettings } from 'redux/selectors/settings';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import SettingsPage from './view'; import SettingsPage from './view';
const select = (state) => ({ const select = (state) => ({
daemonSettings: selectDaemonSettings(state), daemonSettings: selectDaemonSettings(state),
isAuthenticated: selectUserVerifiedEmail(state),
}); });
const perform = (dispatch) => ({ const perform = (dispatch) => ({

View file

@ -14,7 +14,6 @@ type DaemonSettings = {
type Props = { type Props = {
daemonSettings: DaemonSettings, daemonSettings: DaemonSettings,
isAuthenticated: boolean,
enterSettings: () => void, enterSettings: () => void,
exitSettings: () => void, exitSettings: () => void,
}; };

View file

@ -1,6 +1,5 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectFollowedTags } from 'redux/selectors/tags'; import { selectFollowedTags } from 'redux/selectors/tags';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectSubscriptions } from 'redux/selectors/subscriptions'; import { selectSubscriptions } from 'redux/selectors/subscriptions';
import DiscoverPage from './view'; import DiscoverPage from './view';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
@ -9,7 +8,6 @@ import * as SETTINGS from 'constants/settings';
const select = (state) => ({ const select = (state) => ({
followedTags: selectFollowedTags(state), followedTags: selectFollowedTags(state),
subscribedChannels: selectSubscriptions(state), subscribedChannels: selectSubscriptions(state),
email: selectUserVerifiedEmail(state),
tileLayout: makeSelectClientSetting(SETTINGS.TILE_LAYOUT)(state), tileLayout: makeSelectClientSetting(SETTINGS.TILE_LAYOUT)(state),
}); });

View file

@ -1,12 +0,0 @@
import { connect } from 'react-redux';
import { selectYoutubeChannels } from 'redux/selectors/user';
import { doUserFetch } from 'redux/actions/user';
import CreatorDashboardPage from './view';
const select = state => ({
youtubeChannels: selectYoutubeChannels(state),
});
export default connect(select, {
doUserFetch,
})(CreatorDashboardPage);

View file

@ -1,216 +0,0 @@
// @flow
import { SITE_NAME, DOMAIN } from 'config';
import * as PAGES from 'constants/pages';
import SUPPORTED_LANGUAGES from 'constants/supported_languages';
import React from 'react';
import Page from 'component/page';
import Button from 'component/button';
import Card from 'component/common/card';
import I18nMessage from 'component/i18nMessage';
import { Form, FormField } from 'component/common/form';
import { INVALID_NAME_ERROR } from 'constants/claim';
import { isNameValid } from 'util/lbryURI';
import { Lbryio } from 'lbryinc';
import { useHistory } from 'react-router';
import YoutubeTransferStatus from 'component/youtubeTransferStatus';
import Nag from 'component/common/nag';
import { getDefaultLanguage, sortLanguageMap } from 'util/default-languages';
const STATUS_TOKEN_PARAM = 'status_token';
const ERROR_MESSAGE_PARAM = 'error_message';
const NEW_CHANNEL_PARAM = 'new_channel';
type Props = {
youtubeChannels: ?Array<{ transfer_state: string, sync_status: string }>,
doUserFetch: () => void,
inSignUpFlow?: boolean,
doToggleInterestedInYoutubeSync: () => void,
};
export default function YoutubeSync(props: Props) {
const { youtubeChannels, doUserFetch, inSignUpFlow = false, doToggleInterestedInYoutubeSync } = props;
const {
location: { search, pathname },
push,
replace,
} = useHistory();
const urlParams = new URLSearchParams(search);
const statusToken = urlParams.get(STATUS_TOKEN_PARAM);
const errorMessage = urlParams.get(ERROR_MESSAGE_PARAM);
const newChannelParam = urlParams.get(NEW_CHANNEL_PARAM);
const [channel, setChannel] = React.useState('');
const [language, setLanguage] = React.useState(getDefaultLanguage());
const [nameError, setNameError] = React.useState(undefined);
const [acknowledgedTerms, setAcknowledgedTerms] = React.useState(false);
const [addingNewChannel, setAddingNewChannel] = React.useState(newChannelParam);
const hasYoutubeChannels = youtubeChannels && youtubeChannels.length > 0;
React.useEffect(() => {
const urlParamsInEffect = new URLSearchParams(search);
if (!urlParamsInEffect.get('reset_scroll')) {
urlParamsInEffect.append('reset_scroll', 'youtube');
}
replace(`?${urlParamsInEffect.toString()}`);
}, [pathname, search]);
React.useEffect(() => {
if (statusToken && !hasYoutubeChannels) {
doUserFetch();
}
}, [statusToken, hasYoutubeChannels, doUserFetch]);
React.useEffect(() => {
if (!newChannelParam) {
setAddingNewChannel(false);
}
}, [newChannelParam]);
function handleCreateChannel() {
Lbryio.call('yt', 'new', {
type: 'sync',
immediate_sync: true,
channel_language: language,
desired_lbry_channel_name: `@${channel}`,
return_url: `https://${DOMAIN}/$/${inSignUpFlow ? PAGES.AUTH : PAGES.YOUTUBE_SYNC}`,
}).then((ytAuthUrl) => {
// react-router isn't needed since it's a different domain
window.location.href = ytAuthUrl;
});
}
function handleChannelChange(e) {
const { value } = e.target;
setChannel(value);
if (!isNameValid(value)) {
setNameError(INVALID_NAME_ERROR);
} else {
setNameError();
}
}
function handleNewChannel() {
urlParams.append('new_channel', 'true');
push(`${pathname}?${urlParams.toString()}`);
setAddingNewChannel(true);
}
const Wrapper = (props: { children: any }) => {
return inSignUpFlow ? (
<>{props.children}</>
) : (
<Page noSideNavigation authPage>
{props.children}
</Page>
);
};
return (
<Wrapper>
<div className="main__channel-creation">
{hasYoutubeChannels && !addingNewChannel ? (
<YoutubeTransferStatus alwaysShow addNewChannel={handleNewChannel} />
) : (
<Card
title={__('Sync your YouTube channel to %site_name%', { site_name: 'LBRY' })}
subtitle={__('Get your YouTube videos in front of the %site_name% audience.', {
site_name: 'LBRY',
})}
actions={
<Form onSubmit={handleCreateChannel}>
<fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix">
<fieldset-section>
<label htmlFor="auth_first_channel">
{nameError ? (
<span className="error__text">{nameError}</span>
) : (
__('Your %site_name% channel name', { site_name: 'LBRY' })
)}
</label>
<div className="form-field__prefix">@</div>
</fieldset-section>
<FormField
autoFocus
placeholder={__('channel')}
type="text"
name="yt_sync_channel"
className="form-field--short"
value={channel}
onChange={handleChannelChange}
/>
</fieldset-group>
<FormField
name="language_select"
type="select"
label={__('Channel language')}
onChange={(event) => setLanguage(event.target.value)}
value={language}
>
{sortLanguageMap(SUPPORTED_LANGUAGES).map(([langKey, langName]) => (
<option key={langKey} value={langKey}>
{langName}
</option>
))}
</FormField>
<FormField
type="checkbox"
name="yt_sync_terms"
checked={acknowledgedTerms}
onChange={() => setAcknowledgedTerms(!acknowledgedTerms)}
label={
<I18nMessage
tokens={{
terms: (
<Button button="link" label={__('these terms')} href="https://lbry.com/faq/youtube-terms" />
),
faq: (
<Button
button="link"
label={__('how the program works')}
href="https://lbry.com/faq/youtube"
/>
),
site_name: SITE_NAME,
}}
>
I want to sync my content to %site_name% and the LBRY network and agree to %terms%. I have also
read and understand %faq%.
</I18nMessage>
}
/>
<div className="section__actions">
<Button
button="primary"
type="submit"
disabled={nameError || !channel || !acknowledgedTerms}
label={__('Claim Now')}
/>
{inSignUpFlow && !errorMessage && (
<Button button="link" label={__('Skip')} onClick={() => doToggleInterestedInYoutubeSync()} />
)}
{errorMessage && <Button button="link" label={__('Skip')} navigate={`/$/${PAGES.REWARDS}`} />}
</div>
<div className="help--card-actions">
<I18nMessage
tokens={{
learn_more: <Button button="link" label={__('Learn more')} href="https://lbry.com/faq/youtube" />,
}}
>
This will verify you are an active YouTuber. Channel names cannot be changed once chosen, please be
extra careful. Additional instructions will be emailed to you after you verify your email on the
next page. %learn_more%.
</I18nMessage>
</div>
</Form>
}
nag={errorMessage && <Nag message={errorMessage} type="error" relative />}
/>
)}
</div>
</Wrapper>
);
}

View file

@ -1,6 +1,7 @@
import { combineReducers } from 'redux'; import { combineReducers } from 'redux';
import { connectRouter } from 'connected-react-router'; import { connectRouter } from 'connected-react-router';
import { costInfoReducer, blacklistReducer, filteredReducer, statsReducer } from 'lbryinc'; import { costInfoReducer, blacklistReducer, filteredReducer, statsReducer } from 'lbryinc';
import { lbrysyncReducer } from 'redux/reducers/lbrysync';
import { claimsReducer } from 'redux/reducers/claims'; import { claimsReducer } from 'redux/reducers/claims';
import { fileInfoReducer } from 'redux/reducers/file_info'; import { fileInfoReducer } from 'redux/reducers/file_info';
import { walletReducer } from 'redux/reducers/wallet'; import { walletReducer } from 'redux/reducers/wallet';
@ -49,4 +50,5 @@ export default (history) =>
wallet: walletReducer, wallet: walletReducer,
sync: syncReducer, sync: syncReducer,
collections: collectionsReducer, collections: collectionsReducer,
lbrysync: lbrysyncReducer,
}); });

View file

@ -0,0 +1,49 @@
// @flow
import * as ACTIONS from 'constants/action_types';
import Lbrysync from 'lbrysync';
// register an email (eventually username)
export const doLbrysyncRegister = (email: string, password: string) => async (dispatch: Dispatch) => {
const { register } = Lbrysync;
// started
dispatch({
type: ACTIONS.LSYNC_REGISTER_STARTED,
});
const resultIfError = await register(email, password);
if (!resultIfError) {
dispatch({
type: ACTIONS.LSYNC_REGISTER_COMPLETED,
data: email,
});
} else {
dispatch({
type: ACTIONS.LSYNC_REGISTER_FAILED,
data: resultIfError,
});
}
};
// get token given username/password
export const doLbrysyncAuthenticate =
(email: string, password: string, deviceId: string) => async (dispatch: Dispatch) => {
const { getAuthToken } = Lbrysync;
// started
dispatch({
type: ACTIONS.LSYNC_AUTH_STARTED,
});
const result: { token?: string, error?: string } = await getAuthToken(email, password, deviceId);
if (result.token) {
dispatch({
type: ACTIONS.LSYNC_AUTH_COMPLETED,
data: result.token,
});
} else if (result.error) {
dispatch({
type: ACTIONS.LSYNC_AUTH_FAILED,
data: result.error,
});
}
};

View file

@ -4,7 +4,6 @@ import { doToast } from 'redux/actions/notifications';
import * as ACTIONS from 'constants/action_types'; import * as ACTIONS from 'constants/action_types';
import { selectUnclaimedRewards } from 'redux/selectors/rewards'; import { selectUnclaimedRewards } from 'redux/selectors/rewards';
import { selectUserIsRewardApproved } from 'redux/selectors/user'; import { selectUserIsRewardApproved } from 'redux/selectors/user';
// import { doFetchInviteStatus } from 'redux/actions/user';
import rewards from 'rewards'; import rewards from 'rewards';
export function doRewardList() { export function doRewardList() {

View file

@ -6,7 +6,7 @@ import { batchActions } from 'util/batch-actions';
import * as ACTIONS from 'constants/action_types'; import * as ACTIONS from 'constants/action_types';
import { doClaimRewardType, doRewardList } from 'redux/actions/rewards'; import { doClaimRewardType, doRewardList } from 'redux/actions/rewards';
import { selectEmailToVerify, selectPhoneToVerify, selectUserCountryCode, selectUser } from 'redux/selectors/user'; import { selectUser } from 'redux/selectors/user';
import rewards from 'rewards'; import rewards from 'rewards';
import { Lbryio } from 'lbryinc'; import { Lbryio } from 'lbryinc';
import { DOMAIN } from 'config'; import { DOMAIN } from 'config';
@ -14,37 +14,6 @@ import { getDefaultLanguage } from 'util/default-languages';
const AUTH_IN_PROGRESS = 'authInProgress'; const AUTH_IN_PROGRESS = 'authInProgress';
export let sessionStorageAvailable = false; export let sessionStorageAvailable = false;
export function doFetchInviteStatus(shouldCallRewardList = true) {
return (dispatch) => {
dispatch({
type: ACTIONS.USER_INVITE_STATUS_FETCH_STARTED,
});
Promise.all([Lbryio.call('user', 'invite_status'), Lbryio.call('user_referral_code', 'list')])
.then(([status, code]) => {
if (shouldCallRewardList) {
dispatch(doRewardList());
}
dispatch({
type: ACTIONS.USER_INVITE_STATUS_FETCH_SUCCESS,
data: {
invitesRemaining: status.invites_remaining ? status.invites_remaining : 0,
invitees: status.invitees,
referralLink: `${Lbryio.CONNECTION_STRING}user/refer?r=${code}`,
referralCode: code,
},
});
})
.catch((error) => {
dispatch({
type: ACTIONS.USER_INVITE_STATUS_FETCH_FAILURE,
data: { error },
});
});
};
}
export function doInstallNew(appVersion, os = null, firebaseToken = null, callbackForUsersWhoAreSharingData, domain) { export function doInstallNew(appVersion, os = null, firebaseToken = null, callbackForUsersWhoAreSharingData, domain) {
const payload = { app_version: appVersion, domain }; const payload = { app_version: appVersion, domain };
if (firebaseToken) { if (firebaseToken) {
@ -211,95 +180,6 @@ export function doUserPhoneNew(phone, countryCode) {
}; };
} }
export function doUserPhoneVerifyFailure(error) {
return {
type: ACTIONS.USER_PHONE_VERIFY_FAILURE,
data: { error },
};
}
export function doUserPhoneVerify(verificationCode) {
return (dispatch, getState) => {
const phoneNumber = selectPhoneToVerify(getState());
const countryCode = selectUserCountryCode(getState());
dispatch({
type: ACTIONS.USER_PHONE_VERIFY_STARTED,
code: verificationCode,
});
Lbryio.call(
'user',
'phone_number_confirm',
{
verification_code: verificationCode,
phone_number: phoneNumber,
country_code: countryCode,
},
'post'
)
.then((user) => {
if (user.is_identity_verified) {
dispatch({
type: ACTIONS.USER_PHONE_VERIFY_SUCCESS,
data: { user },
});
dispatch(doClaimRewardType(rewards.TYPE_NEW_USER));
}
})
.catch((error) => dispatch(doUserPhoneVerifyFailure(error)));
};
}
export function doUserEmailToVerify(email) {
return (dispatch) => {
dispatch({
type: ACTIONS.USER_EMAIL_VERIFY_SET,
data: { email },
});
};
}
export function doUserEmailNew(email) {
return (dispatch) => {
dispatch({
type: ACTIONS.USER_EMAIL_NEW_STARTED,
email,
});
const success = () => {
dispatch({
type: ACTIONS.USER_EMAIL_NEW_SUCCESS,
data: { email },
});
dispatch(doUserFetch());
};
const failure = (error) => {
dispatch({
type: ACTIONS.USER_EMAIL_NEW_FAILURE,
data: { error },
});
};
Lbryio.call('user_email', 'new', { email, send_verification_email: true }, 'post')
.catch((error) => {
if (error.response && error.response.status === 409) {
dispatch({
type: ACTIONS.USER_EMAIL_NEW_EXISTS,
});
return Lbryio.call('user_email', 'resend_token', { email, only_if_expired: true }, 'post').then(
success,
failure
);
}
throw error;
})
.then(success, failure);
};
}
export function doUserCheckIfEmailExists(email) { export function doUserCheckIfEmailExists(email) {
return (dispatch) => { return (dispatch) => {
dispatch({ dispatch({
@ -530,41 +410,6 @@ export function doUserEmailVerifyFailure(error) {
}; };
} }
export function doUserEmailVerify(verificationToken, recaptcha) {
return (dispatch, getState) => {
const email = selectEmailToVerify(getState());
dispatch({
type: ACTIONS.USER_EMAIL_VERIFY_STARTED,
code: verificationToken,
recaptcha,
});
Lbryio.call(
'user_email',
'confirm',
{
verification_token: verificationToken,
email,
recaptcha,
},
'post'
)
.then((userEmail) => {
if (userEmail.is_verified) {
dispatch({
type: ACTIONS.USER_EMAIL_VERIFY_SUCCESS,
data: { email },
});
dispatch(doUserFetch());
} else {
throw new Error('Your email is still not verified.'); // shouldn't happen
}
})
.catch((error) => dispatch(doUserEmailVerifyFailure(error)));
};
}
export function doFetchAccessToken() { export function doFetchAccessToken() {
return (dispatch) => { return (dispatch) => {
const success = (token) => const success = (token) =>
@ -576,34 +421,6 @@ export function doFetchAccessToken() {
}; };
} }
export function doUserIdentityVerify(stripeToken) {
return (dispatch) => {
dispatch({
type: ACTIONS.USER_IDENTITY_VERIFY_STARTED,
token: stripeToken,
});
Lbryio.call('user', 'verify_identity', { stripe_token: stripeToken }, 'post')
.then((user) => {
if (user.is_identity_verified) {
dispatch({
type: ACTIONS.USER_IDENTITY_VERIFY_SUCCESS,
data: { user },
});
dispatch(doClaimRewardType(rewards.TYPE_NEW_USER));
} else {
throw new Error('Your identity is still not verified. This should not happen.'); // shouldn't happen
}
})
.catch((error) => {
dispatch({
type: ACTIONS.USER_IDENTITY_VERIFY_FAILURE,
data: { error: error.toString() },
});
});
};
}
export function doUserSetReferrerReset() { export function doUserSetReferrerReset() {
return (dispatch) => { return (dispatch) => {
dispatch({ dispatch({

View file

@ -0,0 +1,47 @@
import * as ACTIONS from 'constants/action_types';
import { handleActions } from 'util/redux-utils';
const defaultState = {
registering: false,
registeredEmail: null,
registerError: null,
syncProvider: null,
isAuthenticating: false,
authError: null,
authToken: null, // store this elsewhere?
};
export const lbrysyncReducer = handleActions(
{
// Register
[ACTIONS.LSYNC_REGISTER_STARTED]: (state) => ({
...state,
registering: true,
}),
[ACTIONS.LSYNC_REGISTER_COMPLETED]: (state, action) => ({
...state,
registeredEmail: action.data,
}),
[ACTIONS.LSYNC_REGISTER_FAILED]: (state) => ({
...state,
registeredEmail: null,
registering: false,
}),
// Auth
[ACTIONS.LSYNC_AUTH_STARTED]: (state) => ({
...state,
isAuthenticating: true,
}),
[ACTIONS.LSYNC_AUTH_COMPLETED]: (state, action) => ({
...state,
authToken: action.data,
}),
[ACTIONS.LSYNC_AUTH_FAILED]: (state, action) => ({
...state,
authError: action.data,
isAuthenticating: false,
}),
// ...
},
defaultState
);

View file

@ -0,0 +1,16 @@
import { createSelector } from 'reselect';
const selectState = (state) => state.lbrysync || {};
export const selectLbrySyncRegistering = createSelector(selectState, (state) => state.registering);
export const selectLbrySyncEmail = createSelector(selectState, (state) => state.registeredEmail);
export const selectLbrySyncRegisterError = createSelector(selectState, (state) => state.registerError);
// probably shouldn't store this here.
export const selectLbrySyncToken = createSelector(selectState, (state) => state.registering);
export const selectLbrySyncIsAuthenticating = createSelector(selectState, (state) => state.isAuthenticating);
export const selectLbrySyncAuthError = createSelector(selectState, (state) => state.authError);

View file

@ -2,8 +2,6 @@ import { createSelector } from 'reselect';
export const selectState = (state) => state.user || {}; export const selectState = (state) => state.user || {};
export const selectAuthenticationIsPending = createSelector(selectState, (state) => state.authenticationIsPending);
export const selectUserIsPending = createSelector(selectState, (state) => state.userIsPending); export const selectUserIsPending = createSelector(selectState, (state) => state.userIsPending);
export const selectUser = createSelector(selectState, (state) => state.user); export const selectUser = createSelector(selectState, (state) => state.user);
@ -21,8 +19,6 @@ export const selectUserEmail = createSelector(selectUser, (user) =>
user ? user.primary_email || user.latest_claimed_email : null user ? user.primary_email || user.latest_claimed_email : null
); );
export const selectUserPhone = createSelector(selectUser, (user) => (user ? user.phone_number : null));
export const selectUserCountryCode = createSelector(selectUser, (user) => (user ? user.country_code : null)); export const selectUserCountryCode = createSelector(selectUser, (user) => (user ? user.country_code : null));
export const selectEmailToVerify = createSelector( export const selectEmailToVerify = createSelector(
@ -31,12 +27,6 @@ export const selectEmailToVerify = createSelector(
(state, userEmail) => state.emailToVerify || userEmail (state, userEmail) => state.emailToVerify || userEmail
); );
export const selectPhoneToVerify = createSelector(
selectState,
selectUserPhone,
(state, userPhone) => state.phoneToVerify || userPhone
);
export const selectYoutubeChannels = createSelector(selectUser, (user) => (user ? user.youtube_channels : null)); export const selectYoutubeChannels = createSelector(selectUser, (user) => (user ? user.youtube_channels : null));
export const selectUserIsRewardApproved = createSelector(selectUser, (user) => user && user.is_reward_approved); export const selectUserIsRewardApproved = createSelector(selectUser, (user) => user && user.is_reward_approved);

View file

@ -128,7 +128,6 @@ export function GetLinksData(
all: any, // HomepageData type? all: any, // HomepageData type?
isLargeScreen: boolean, isLargeScreen: boolean,
isHomepage?: boolean = false, isHomepage?: boolean = false,
authenticated?: boolean,
showPersonalizedChannels?: boolean, showPersonalizedChannels?: boolean,
showPersonalizedTags?: boolean, showPersonalizedTags?: boolean,
subscribedChannels?: Array<Subscription>, subscribedChannels?: Array<Subscription>,
@ -170,6 +169,7 @@ export function GetLinksData(
// ************************************************************************** // **************************************************************************
// @if CUSTOM_HOMEPAGE='false' // @if CUSTOM_HOMEPAGE='false'
/*
const YOUTUBER_CHANNEL_IDS = [ const YOUTUBER_CHANNEL_IDS = [
'fb364ef587872515f545a5b4b3182b58073f230f', 'fb364ef587872515f545a5b4b3182b58073f230f',
'589276465a23c589801d874f484cc39f307d7ec7', '589276465a23c589801d874f484cc39f307d7ec7',
@ -253,21 +253,7 @@ export function GetLinksData(
'ff80e24f41a2d706c70df9779542cba4715216c9', 'ff80e24f41a2d706c70df9779542cba4715216c9',
'e8f68563d242f6ac9784dcbc41dd86c28a9391d6', 'e8f68563d242f6ac9784dcbc41dd86c28a9391d6',
]; ];
*/
const YOUTUBE_CREATOR_ROW = {
title: __('CableTube Escape Artists'),
link: `/$/${PAGES.DISCOVER}?${CS.CLAIM_TYPE}=${CS.CLAIM_STREAM}&${CS.CHANNEL_IDS_KEY}=${YOUTUBER_CHANNEL_IDS.join(
','
)}`,
options: {
claimType: ['stream'],
orderBy: CS.ORDER_BY_NEW_VALUE,
pageSize: getPageSize(12),
channelIds: YOUTUBER_CHANNEL_IDS,
limitClaimsPerChannel: 1,
releaseTime: `>${Math.floor(moment().subtract(1, 'months').startOf('week').unix())}`,
},
};
const TOP_CONTENT_TODAY = { const TOP_CONTENT_TODAY = {
title: __('Top Content from Today'), title: __('Top Content from Today'),
@ -351,9 +337,6 @@ export function GetLinksData(
} }
if (!CUSTOM_HOMEPAGE) { if (!CUSTOM_HOMEPAGE) {
if (!authenticated) {
rowData.push(YOUTUBE_CREATOR_ROW);
}
rowData.push(TOP_CONTENT_TODAY); rowData.push(TOP_CONTENT_TODAY);
if (language !== 'en') { if (language !== 'en') {
rowData.push(LANGUAGE_CATEGORY); rowData.push(LANGUAGE_CATEGORY);