Compare commits
6 commits
master
...
new-sync-d
Author | SHA1 | Date | |
---|---|---|---|
|
ecfbaea8b1 | ||
|
16fe53c47d | ||
|
df6be2ac8f | ||
|
4c40b5a07f | ||
|
2ca26c9332 | ||
|
0105f2516f |
83 changed files with 316 additions and 2678 deletions
|
@ -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,
|
||||||
|
|
|
@ -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--"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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),
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
|
|
@ -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;
|
|
|
@ -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);
|
|
|
@ -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;
|
|
|
@ -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));
|
|
|
@ -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;
|
|
|
@ -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);
|
|
|
@ -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;
|
|
|
@ -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);
|
|
|
@ -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;
|
|
|
@ -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);
|
|
|
@ -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;
|
|
|
@ -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);
|
|
|
@ -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;
|
|
|
@ -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);
|
|
|
@ -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;
|
|
|
@ -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 |
|
@ -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;
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.')}>
|
||||||
|
|
|
@ -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),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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);
|
|
|
@ -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;
|
|
|
@ -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);
|
|
|
@ -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;
|
|
|
@ -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);
|
|
|
@ -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;
|
|
|
@ -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,
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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
83
ui/lbrysync.js
Normal 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;
|
|
@ -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);
|
|
|
@ -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;
|
|
|
@ -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);
|
|
|
@ -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;
|
|
|
@ -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:
|
||||||
|
|
|
@ -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);
|
|
|
@ -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;
|
|
|
@ -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);
|
|
|
@ -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;
|
|
|
@ -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),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import FourOhFourPage from './view';
|
|
||||||
|
|
||||||
const select = state => ({});
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
select,
|
|
||||||
null
|
|
||||||
)(FourOhFourPage);
|
|
|
@ -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;
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
|
|
@ -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;
|
|
|
@ -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));
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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);
|
|
|
@ -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;
|
|
|
@ -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);
|
|
|
@ -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;
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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) => ({
|
||||||
|
|
|
@ -14,7 +14,6 @@ type DaemonSettings = {
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
daemonSettings: DaemonSettings,
|
daemonSettings: DaemonSettings,
|
||||||
isAuthenticated: boolean,
|
|
||||||
enterSettings: () => void,
|
enterSettings: () => void,
|
||||||
exitSettings: () => void,
|
exitSettings: () => void,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
49
ui/redux/actions/lbrysync.js
Normal file
49
ui/redux/actions/lbrysync.js
Normal 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -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() {
|
||||||
|
|
|
@ -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({
|
||||||
|
|
47
ui/redux/reducers/lbrysync.js
Normal file
47
ui/redux/reducers/lbrysync.js
Normal 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
|
||||||
|
);
|
16
ui/redux/selectors/lbrysync.js
Normal file
16
ui/redux/selectors/lbrysync.js
Normal 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);
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue