add youtube sync to first run
This commit is contained in:
parent
dec63d7a2e
commit
bc89d774ba
18 changed files with 348 additions and 56 deletions
|
@ -508,10 +508,10 @@
|
||||||
"Policies": "Policies",
|
"Policies": "Policies",
|
||||||
"Confirm your account": "Confirm your account",
|
"Confirm your account": "Confirm your account",
|
||||||
"Start Over": "Start Over",
|
"Start Over": "Start Over",
|
||||||
"Your YouTube Channel": "Your YouTube Channel",
|
"Your YouTube channel": "Your YouTube channel",
|
||||||
"Your YouTube Channels": "Your YouTube Channels",
|
"Your YouTube channels": "Your YouTube channels",
|
||||||
"Your videos are currently being transferred. There is nothing else for you to do.": "Your videos are currently being transferred. There is nothing else for you to do.",
|
"Your videos are currently being transferred. There is nothing else for you to do.": "Your videos are currently being transferred. There is nothing else for you to do.",
|
||||||
"Please check back later.": "Please check back later.",
|
"Please check back later. This may take up to 1 week.": "Please check back later. This may take up to 1 week.",
|
||||||
"%channelName% is not yet ready to be transferred. Please allow up to one week, though it is frequently faster.": "%channelName% is not yet ready to be transferred. Please allow up to one week, though it is frequently faster.",
|
"%channelName% is not yet ready to be transferred. Please allow up to one week, though it is frequently faster.": "%channelName% is not yet ready to be transferred. Please allow up to one week, though it is frequently faster.",
|
||||||
"here": "here",
|
"here": "here",
|
||||||
"%channelName% is not ready to be transferred. You can check the status %statusLink% or check back later.": "%channelName% is not ready to be transferred. You can check the status %statusLink% or check back later.",
|
"%channelName% is not ready to be transferred. You can check the status %statusLink% or check back later.": "%channelName% is not ready to be transferred. You can check the status %statusLink% or check back later.",
|
||||||
|
|
|
@ -739,6 +739,7 @@ export const icons = {
|
||||||
<polyline points="22 4 12 14.01 9 11.01" />
|
<polyline points="22 4 12 14.01 9 11.01" />
|
||||||
</g>
|
</g>
|
||||||
),
|
),
|
||||||
|
[ICONS.NOT_COMPLETED]: buildIcon(<circle cx="12" cy="12" r="10" />),
|
||||||
[ICONS.PINNED]: buildIcon(
|
[ICONS.PINNED]: buildIcon(
|
||||||
<g>
|
<g>
|
||||||
<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" />
|
<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" />
|
||||||
|
@ -819,4 +820,10 @@ export const icons = {
|
||||||
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
|
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
|
||||||
</g>
|
</g>
|
||||||
),
|
),
|
||||||
|
[ICONS.YOUTUBE]: buildIcon(
|
||||||
|
<g>
|
||||||
|
<path d="M22.54 6.42a2.78 2.78 0 0 0-1.94-2C18.88 4 12 4 12 4s-6.88 0-8.6.46a2.78 2.78 0 0 0-1.94 2A29 29 0 0 0 1 11.75a29 29 0 0 0 .46 5.33A2.78 2.78 0 0 0 3.4 19c1.72.46 8.6.46 8.6.46s6.88 0 8.6-.46a2.78 2.78 0 0 0 1.94-2 29 29 0 0 0 .46-5.25 29 29 0 0 0-.46-5.33z" />
|
||||||
|
<polygon points="9.75 15.02 15.5 11.75 9.75 8.48 9.75 15.02" />
|
||||||
|
</g>
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
|
@ -177,7 +177,7 @@ const Header = (props: Props) => {
|
||||||
label={(backLabel && backLabel) || __('Cancel')}
|
label={(backLabel && backLabel) || __('Cancel')}
|
||||||
icon={ICONS.ARROW_LEFT}
|
icon={ICONS.ARROW_LEFT}
|
||||||
/>
|
/>
|
||||||
{backTitle && <h1 className={'card__title'}>{isMobile ? simpleBackTitle || backTitle : backTitle}</h1>}
|
{backTitle && <h1 className="header__auth-title">{isMobile ? simpleBackTitle || backTitle : backTitle}</h1>}
|
||||||
<Button
|
<Button
|
||||||
aria-label={__('Your wallet')}
|
aria-label={__('Your wallet')}
|
||||||
navigate={`/$/${PAGES.WALLET}`}
|
navigate={`/$/${PAGES.WALLET}`}
|
||||||
|
|
|
@ -19,7 +19,7 @@ function RewardAuthIntro(props: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
title={title || __('Log in to %SITE_NAME% to Earn Rewards', { SITE_NAME })}
|
title={title || __('Log in to %SITE_NAME% to earn rewards', { SITE_NAME })}
|
||||||
subtitle={
|
subtitle={
|
||||||
<I18nMessage
|
<I18nMessage
|
||||||
tokens={{
|
tokens={{
|
||||||
|
|
|
@ -46,6 +46,7 @@ import ChannelNew from 'page/channelNew';
|
||||||
import BuyPage from 'page/buy';
|
import BuyPage from 'page/buy';
|
||||||
import NotificationsPage from 'page/notifications';
|
import NotificationsPage from 'page/notifications';
|
||||||
import SignInWalletPasswordPage from 'page/signInWalletPassword';
|
import SignInWalletPasswordPage from 'page/signInWalletPassword';
|
||||||
|
import YoutubeSyncPage from 'page/youtubeSync';
|
||||||
import { LINKED_COMMENT_QUERY_PARAM } from 'constants/comment';
|
import { LINKED_COMMENT_QUERY_PARAM } from 'constants/comment';
|
||||||
import { parseURI } from 'lbry-redux';
|
import { parseURI } from 'lbry-redux';
|
||||||
import { SITE_TITLE, WELCOME_VERSION } from 'config';
|
import { SITE_TITLE, WELCOME_VERSION } from 'config';
|
||||||
|
@ -190,6 +191,7 @@ function AppRouter(props: Props) {
|
||||||
<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.WELCOME}`} exact component={Welcome} />
|
<Route path={`/$/${PAGES.WELCOME}`} exact component={Welcome} />
|
||||||
|
<Route path={`/$/${PAGES.YOUTUBE_SYNC}`} exact component={YoutubeSyncPage} />
|
||||||
|
|
||||||
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
|
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
|
||||||
{/* @if TARGET='app' */}
|
{/* @if TARGET='app' */}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { isNameValid } from 'lbry-redux';
|
import { isNameValid } from 'lbry-redux';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import { Form, FormField } from 'component/common/form';
|
import { Form, FormField } from 'component/common/form';
|
||||||
import { INVALID_NAME_ERROR } from 'constants/claim';
|
import { INVALID_NAME_ERROR } from 'constants/claim';
|
||||||
import Card from 'component/common/card';
|
import Card from 'component/common/card';
|
||||||
|
import I18nMessage from 'component/i18nMessage';
|
||||||
export const DEFAULT_BID_FOR_FIRST_CHANNEL = 0.01;
|
export const DEFAULT_BID_FOR_FIRST_CHANNEL = 0.01;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -78,6 +80,21 @@ function UserFirstChannel(props: Props) {
|
||||||
label={creatingChannel || claimingReward ? __('Creating') : __('Create')}
|
label={creatingChannel || claimingReward ? __('Creating') : __('Create')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="help--card-actions">
|
||||||
|
<I18nMessage
|
||||||
|
tokens={{
|
||||||
|
sync_channel: (
|
||||||
|
<Button
|
||||||
|
button="link"
|
||||||
|
label={__('Sync it and skip this step')}
|
||||||
|
navigate={`/$/${PAGES.YOUTUBE_SYNC}`}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Have a YouTube channel? %sync_channel%.
|
||||||
|
</I18nMessage>
|
||||||
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as PAGES from 'constants/pages';
|
import * as ICONS from 'constants/icons';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import ClaimPreview from 'component/claimPreview';
|
import ClaimPreview from 'component/claimPreview';
|
||||||
import Card from 'component/common/card';
|
import Card from 'component/common/card';
|
||||||
import { YOUTUBE_STATUSES } from 'lbryinc';
|
import { YOUTUBE_STATUSES } from 'lbryinc';
|
||||||
import { buildURI } from 'lbry-redux';
|
import { buildURI } from 'lbry-redux';
|
||||||
import I18nMessage from 'component/i18nMessage';
|
import Spinner from 'component/spinner';
|
||||||
|
import Icon from 'component/common/icon';
|
||||||
const STATUS_URL = 'https://lbry.com/youtube/status/';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
youtubeChannels: Array<any>,
|
youtubeChannels: Array<any>,
|
||||||
|
@ -17,7 +17,8 @@ type Props = {
|
||||||
updateUser: () => void,
|
updateUser: () => void,
|
||||||
checkYoutubeTransfer: () => void,
|
checkYoutubeTransfer: () => void,
|
||||||
videosImported: ?Array<number>, // [currentAmountImported, totalAmountToImport]
|
videosImported: ?Array<number>, // [currentAmountImported, totalAmountToImport]
|
||||||
hideChannelLink: boolean,
|
alwaysShow: boolean,
|
||||||
|
addNewChannel?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function YoutubeTransferStatus(props: Props) {
|
export default function YoutubeTransferStatus(props: Props) {
|
||||||
|
@ -28,15 +29,21 @@ export default function YoutubeTransferStatus(props: Props) {
|
||||||
videosImported,
|
videosImported,
|
||||||
checkYoutubeTransfer,
|
checkYoutubeTransfer,
|
||||||
updateUser,
|
updateUser,
|
||||||
hideChannelLink = false,
|
alwaysShow = false,
|
||||||
|
addNewChannel,
|
||||||
} = props;
|
} = props;
|
||||||
const hasChannels = youtubeChannels && youtubeChannels.length;
|
const hasChannels = youtubeChannels && youtubeChannels.length > 0;
|
||||||
const transferEnabled = youtubeChannels.some(status => status.transferable);
|
const transferEnabled = youtubeChannels.some(status => status.transferable);
|
||||||
const hasPendingTransfers = youtubeChannels.some(
|
const hasPendingTransfers = youtubeChannels.some(
|
||||||
status => status.transfer_state === YOUTUBE_STATUSES.PENDING_TRANSFER
|
status => status.transfer_state === YOUTUBE_STATUSES.YOUTUBE_SYNC_PENDING_TRANSFER
|
||||||
);
|
);
|
||||||
const isYoutubeTransferComplete =
|
const isYoutubeTransferComplete =
|
||||||
hasChannels && youtubeChannels.every(channel => channel.transfer_state === YOUTUBE_STATUSES.COMPLETED_TRANSFER);
|
hasChannels &&
|
||||||
|
youtubeChannels.every(
|
||||||
|
channel =>
|
||||||
|
channel.transfer_state === YOUTUBE_STATUSES.YOUTUBE_SYNC_COMPLETED_TRANSFER ||
|
||||||
|
channel.sync_status === YOUTUBE_STATUSES.YOUTUBE_SYNC_ABANDONDED
|
||||||
|
);
|
||||||
|
|
||||||
let total;
|
let total;
|
||||||
let complete;
|
let complete;
|
||||||
|
@ -49,12 +56,14 @@ export default function YoutubeTransferStatus(props: Props) {
|
||||||
const { transferable, transfer_state: transferState, sync_status: syncStatus } = channel;
|
const { transferable, transfer_state: transferState, sync_status: syncStatus } = channel;
|
||||||
if (!transferable) {
|
if (!transferable) {
|
||||||
switch (transferState) {
|
switch (transferState) {
|
||||||
case YOUTUBE_STATUSES.NOT_TRANSFERRED:
|
case YOUTUBE_STATUSES.YOUTUBE_SYNC_NOT_TRANSFERRED:
|
||||||
return syncStatus[0].toUpperCase() + syncStatus.slice(1);
|
return syncStatus[0].toUpperCase() + syncStatus.slice(1);
|
||||||
case YOUTUBE_STATUSES.PENDING_TRANSFER:
|
case YOUTUBE_STATUSES.YOUTUBE_SYNC_PENDING_TRANSFER:
|
||||||
return __('Transfer in progress');
|
return __('Transfer in progress');
|
||||||
case YOUTUBE_STATUSES.COMPLETED_TRANSFER:
|
case YOUTUBE_STATUSES.YOUTUBE_SYNC_COMPLETED_TRANSFER:
|
||||||
return __('Completed transfer');
|
return __('Completed transfer');
|
||||||
|
case YOUTUBE_STATUSES.YOUTUBE_SYNC_ABANDONDED:
|
||||||
|
return __('This channel not eligible to by synced');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return __('Ready to transfer');
|
return __('Ready to transfer');
|
||||||
|
@ -74,28 +83,36 @@ export default function YoutubeTransferStatus(props: Props) {
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
updateUser();
|
||||||
}
|
}
|
||||||
}, [hasPendingTransfers, checkYoutubeTransfer, updateUser]);
|
}, [hasPendingTransfers, checkYoutubeTransfer, updateUser, updateUser]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
hasChannels &&
|
(alwaysShow || (hasChannels && !isYoutubeTransferComplete)) && (
|
||||||
!isYoutubeTransferComplete && (
|
|
||||||
<Card
|
<Card
|
||||||
title={youtubeChannels.length > 1 ? __('Your YouTube Channels') : __('Your YouTube Channel')}
|
title={youtubeChannels.length > 1 ? __('Your YouTube channels') : __('Your YouTube channel')}
|
||||||
subtitle={
|
subtitle={
|
||||||
<span>
|
<span>
|
||||||
{hasPendingTransfers &&
|
{hasPendingTransfers &&
|
||||||
__('Your videos are currently being transferred. There is nothing else for you to do.')}
|
__('Your videos are currently being transferred. There is nothing else for you to do.')}
|
||||||
{transferEnabled && !hasPendingTransfers && __('Your videos are ready to be transferred.')}
|
{transferEnabled && !hasPendingTransfers && __('Your videos are ready to be transferred.')}
|
||||||
{!transferEnabled && !hasPendingTransfers && __('Please check back later.')}
|
{!transferEnabled && !hasPendingTransfers && __('Please check back later. This may take up to 1 week.')}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
body={
|
body={
|
||||||
<section>
|
<section>
|
||||||
{youtubeChannels.map((channel, index) => {
|
{youtubeChannels.map((channel, index) => {
|
||||||
const { lbry_channel_name: channelName, channel_claim_id: claimId, status_token: statusToken } = channel;
|
const { lbry_channel_name: channelName, channel_claim_id: claimId, sync_status: syncStatus } = channel;
|
||||||
const url = buildURI({ channelName, channelClaimId: claimId });
|
const url = buildURI({ channelName, channelClaimId: claimId });
|
||||||
const transferState = getMessage(channel);
|
const transferState = getMessage(channel);
|
||||||
|
const isWaitingForSync =
|
||||||
|
syncStatus === YOUTUBE_STATUSES.YOUTUBE_SYNC_QUEUED ||
|
||||||
|
syncStatus === YOUTUBE_STATUSES.YOUTUBE_SYNC_PENDINGUPGRADE ||
|
||||||
|
syncStatus === YOUTUBE_STATUSES.YOUTUBE_SYNC_SYNCING;
|
||||||
|
|
||||||
|
const isNotEligible = syncStatus === YOUTUBE_STATUSES.YOUTUBE_SYNC_ABANDONDED;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={url} className="card--inline">
|
<div key={url} className="card--inline">
|
||||||
{claimId ? (
|
{claimId ? (
|
||||||
|
@ -106,26 +123,32 @@ export default function YoutubeTransferStatus(props: Props) {
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="section--padded">
|
<div className="section--padded">
|
||||||
<p>
|
{isNotEligible ? (
|
||||||
<I18nMessage
|
<div>{__('%channelName% is not eligible to be synced', { channelName })}</div>
|
||||||
tokens={{
|
) : (
|
||||||
channelName,
|
<div className="progress">
|
||||||
}}
|
<div className="progress__item">
|
||||||
>
|
{__('Claim your handle %handle%', { handle: channelName })}
|
||||||
%channelName% is not yet ready to be transferred. Please allow up to one week, though it is
|
<Icon icon={ICONS.COMPLETED} className="progress__complete-icon--completed" />
|
||||||
frequently faster.
|
</div>
|
||||||
</I18nMessage>
|
<div className="progress__item">
|
||||||
</p>
|
{__('Agree to sync')}{' '}
|
||||||
<p className="help">
|
<Icon icon={ICONS.COMPLETED} className="progress__complete-icon--completed" />
|
||||||
<I18nMessage
|
</div>
|
||||||
tokens={{
|
<div className="progress__item">
|
||||||
statusLink: <Button button="link" href={STATUS_URL + statusToken} label={__('here')} />,
|
{__('Wait for your videos to be synced')}
|
||||||
faqLink: <Button button="link" label={__('FAQ')} href="https://lbry.com/faq/youtube" />,
|
{isWaitingForSync ? (
|
||||||
}}
|
<Spinner type="small" />
|
||||||
>
|
) : (
|
||||||
You can check your status %statusLink%. This %faqLink% explains the program in more detail.
|
<Icon icon={ICONS.COMPLETED} className="progress__complete-icon--completed" />
|
||||||
</I18nMessage>
|
)}
|
||||||
</p>
|
</div>
|
||||||
|
<div className="progress__item">
|
||||||
|
{__('Claim your channel')}
|
||||||
|
<Icon icon={ICONS.NOT_COMPLETED} className={classnames('progress__complete-icon')} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -137,23 +160,30 @@ export default function YoutubeTransferStatus(props: Props) {
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
transferEnabled ? (
|
<>
|
||||||
<div className="card__actions">
|
<div className="section__actions">
|
||||||
<Button
|
<Button
|
||||||
button="primary"
|
button="primary"
|
||||||
disabled={youtubeImportPending}
|
disabled={youtubeImportPending || !transferEnabled}
|
||||||
onClick={claimChannels}
|
onClick={claimChannels}
|
||||||
label={youtubeChannels.length > 1 ? __('Claim Channels') : __('Claim Channel')}
|
label={youtubeChannels.length > 1 ? __('Claim Channels') : __('Claim Channel')}
|
||||||
/>
|
/>
|
||||||
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/youtube#transfer" />
|
{addNewChannel ? (
|
||||||
|
<Button button="link" label={__('Add Another Channel')} onClick={addNewChannel} />
|
||||||
|
) : (
|
||||||
|
<Button button="link" label={__('Learn More')} href="https://lbry.com/faq/youtube#transfer" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : !hideChannelLink ? (
|
|
||||||
<div className="card__actions">
|
<p className="help">
|
||||||
<Button button="primary" navigate={`/$/${PAGES.CHANNELS}`} label={__('View your channels')} />
|
{youtubeChannels.length > 1
|
||||||
</div>
|
? __('You will be able to claim your channels once they finish syncing.')
|
||||||
) : (
|
: __('You will be able to claim your channel once it has finished syncing.')}{' '}
|
||||||
false
|
{addNewChannel && (
|
||||||
)
|
<Button button="link" label={__('Learn More')} href="https://lbry.com/faq/youtube#transfer" />
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -30,6 +30,7 @@ export const WALLET = 'List';
|
||||||
export const PHONE = 'Phone';
|
export const PHONE = 'Phone';
|
||||||
export const COMPLETE = 'Check';
|
export const COMPLETE = 'Check';
|
||||||
export const COMPLETED = 'CheckCircle';
|
export const COMPLETED = 'CheckCircle';
|
||||||
|
export const NOT_COMPLETED = 'Circle';
|
||||||
export const SUBSCRIBE = 'Heart';
|
export const SUBSCRIBE = 'Heart';
|
||||||
export const UNSUBSCRIBE = 'BrokenHeart';
|
export const UNSUBSCRIBE = 'BrokenHeart';
|
||||||
export const UNLOCK = 'Unlock';
|
export const UNLOCK = 'Unlock';
|
||||||
|
@ -115,3 +116,4 @@ export const LBRY_STATUS = 'BarChart';
|
||||||
export const NOTIFICATION = 'Bell';
|
export const NOTIFICATION = 'Bell';
|
||||||
export const LAYOUT = 'Layout';
|
export const LAYOUT = 'Layout';
|
||||||
export const REPLY = 'Reply';
|
export const REPLY = 'Reply';
|
||||||
|
export const YOUTUBE = 'Youtube';
|
||||||
|
|
|
@ -46,3 +46,4 @@ exports.CODE_2257 = '2257';
|
||||||
exports.BUY = 'buy';
|
exports.BUY = 'buy';
|
||||||
exports.CHANNEL_NEW = 'channel/new';
|
exports.CHANNEL_NEW = 'channel/new';
|
||||||
exports.NOTIFICATIONS = 'notifications';
|
exports.NOTIFICATIONS = 'notifications';
|
||||||
|
exports.YOUTUBE_SYNC = 'youtube';
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { selectChannelIsBlocked } from 'redux/selectors/blocked';
|
import { selectChannelIsBlocked } from 'redux/selectors/blocked';
|
||||||
import { selectBlackListedOutpoints, doFetchSubCount, makeSelectSubCountForUri } from 'lbryinc';
|
import { selectBlackListedOutpoints, doFetchSubCount, makeSelectSubCountForUri } from 'lbryinc';
|
||||||
|
import { selectYoutubeChannels } from 'redux/selectors/user';
|
||||||
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
||||||
import { doOpenModal } from 'redux/actions/app';
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
import ChannelPage from './view';
|
import ChannelPage from './view';
|
||||||
|
@ -26,6 +27,7 @@ const select = (state, props) => ({
|
||||||
blackListedOutpoints: selectBlackListedOutpoints(state),
|
blackListedOutpoints: selectBlackListedOutpoints(state),
|
||||||
subCount: makeSelectSubCountForUri(props.uri)(state),
|
subCount: makeSelectSubCountForUri(props.uri)(state),
|
||||||
pending: makeSelectClaimIsPending(props.uri)(state),
|
pending: makeSelectClaimIsPending(props.uri)(state),
|
||||||
|
youtubeChannels: selectYoutubeChannels(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { parseURI } from 'lbry-redux';
|
import { parseURI } from 'lbry-redux';
|
||||||
|
import { YOUTUBE_STATUSES } from 'lbryinc';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import SubscribeButton from 'component/subscribeButton';
|
import SubscribeButton from 'component/subscribeButton';
|
||||||
import BlockButton from 'component/blockButton';
|
import BlockButton from 'component/blockButton';
|
||||||
|
@ -43,6 +45,7 @@ type Props = {
|
||||||
fetchSubCount: string => void,
|
fetchSubCount: string => void,
|
||||||
subCount: number,
|
subCount: number,
|
||||||
pending: boolean,
|
pending: boolean,
|
||||||
|
youtubeChannels: ?Array<{ channel_claim_id: string, sync_status: string, transfer_state: string }>,
|
||||||
};
|
};
|
||||||
|
|
||||||
function ChannelPage(props: Props) {
|
function ChannelPage(props: Props) {
|
||||||
|
@ -59,6 +62,7 @@ function ChannelPage(props: Props) {
|
||||||
fetchSubCount,
|
fetchSubCount,
|
||||||
subCount,
|
subCount,
|
||||||
pending,
|
pending,
|
||||||
|
youtubeChannels,
|
||||||
} = props;
|
} = props;
|
||||||
const {
|
const {
|
||||||
push,
|
push,
|
||||||
|
@ -73,6 +77,18 @@ function ChannelPage(props: Props) {
|
||||||
const { permanent_url: permanentUrl } = claim;
|
const { permanent_url: permanentUrl } = claim;
|
||||||
const claimId = claim.claim_id;
|
const claimId = claim.claim_id;
|
||||||
const formattedSubCount = Number(subCount).toLocaleString();
|
const formattedSubCount = Number(subCount).toLocaleString();
|
||||||
|
const isMyYouTubeChannel =
|
||||||
|
claim &&
|
||||||
|
youtubeChannels &&
|
||||||
|
youtubeChannels.some(({ channel_claim_id, sync_status, transfer_state }) => {
|
||||||
|
if (
|
||||||
|
channel_claim_id === claim.claim_id &&
|
||||||
|
sync_status !== YOUTUBE_STATUSES.YOUTUBE_SYNC_ABANDONDED &&
|
||||||
|
transfer_state !== YOUTUBE_STATUSES.YOUTUBE_SYNC_COMPLETED_TRANSFER
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
let channelIsBlackListed = false;
|
let channelIsBlackListed = false;
|
||||||
|
|
||||||
if (claim && blackListedOutpoints) {
|
if (claim && blackListedOutpoints) {
|
||||||
|
@ -131,6 +147,14 @@ function ChannelPage(props: Props) {
|
||||||
<YoutubeBadge channelClaimId={claimId} />
|
<YoutubeBadge channelClaimId={claimId} />
|
||||||
<header className="channel-cover">
|
<header className="channel-cover">
|
||||||
<div className="channel__quick-actions">
|
<div className="channel__quick-actions">
|
||||||
|
{isMyYouTubeChannel && (
|
||||||
|
<Button
|
||||||
|
button="alt"
|
||||||
|
label={__('Claim Your Channel')}
|
||||||
|
icon={ICONS.YOUTUBE}
|
||||||
|
navigate={`/$/${PAGES.CHANNELS}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{!channelIsBlocked && !channelIsBlackListed && <ShareButton uri={uri} />}
|
{!channelIsBlocked && !channelIsBlackListed && <ShareButton uri={uri} />}
|
||||||
{!channelIsBlocked && <ClaimSupportButton uri={uri} />}
|
{!channelIsBlocked && <ClaimSupportButton uri={uri} />}
|
||||||
{!channelIsBlocked && (!channelIsBlackListed || isSubscribed) && <SubscribeButton uri={permanentUrl} />}
|
{!channelIsBlocked && (!channelIsBlackListed || isSubscribed) && <SubscribeButton uri={permanentUrl} />}
|
||||||
|
|
|
@ -33,7 +33,7 @@ class InvitePage extends React.PureComponent<Props> {
|
||||||
<Page>
|
<Page>
|
||||||
{!authenticated ? (
|
{!authenticated ? (
|
||||||
<RewardAuthIntro
|
<RewardAuthIntro
|
||||||
title={__('Log in to %SITE_NAME% to Earn Rewards From Inviting Your Friends', { SITE_NAME })}
|
title={__('Log in to %SITE_NAME% to earn rewards From Inviting Your Friends', { SITE_NAME })}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
|
12
ui/page/youtubeSync/index.js
Normal file
12
ui/page/youtubeSync/index.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
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);
|
168
ui/page/youtubeSync/view.jsx
Normal file
168
ui/page/youtubeSync/view.jsx
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
// @flow
|
||||||
|
import { SITE_NAME, DOMAIN } from 'config';
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
|
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 'lbry-redux';
|
||||||
|
import { Lbryio } from 'lbryinc';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
import YoutubeTransferStatus from 'component/youtubeTransferStatus';
|
||||||
|
import Nag from 'component/common/nag';
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function YoutubeSync(props: Props) {
|
||||||
|
const { youtubeChannels, doUserFetch } = props;
|
||||||
|
const {
|
||||||
|
location: { search, pathname },
|
||||||
|
push,
|
||||||
|
} = 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 [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(() => {
|
||||||
|
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,
|
||||||
|
desired_lbry_channel_name: `@${channel}`,
|
||||||
|
return_url: `https://${DOMAIN}/$/${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, 'false')) {
|
||||||
|
setNameError(INVALID_NAME_ERROR);
|
||||||
|
} else {
|
||||||
|
setNameError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNewChannel() {
|
||||||
|
urlParams.append('new_channel', 'true');
|
||||||
|
push(`${pathname}?${urlParams.toString()}`);
|
||||||
|
setAddingNewChannel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page noSideNavigation authPage>
|
||||||
|
<div className="main__channel-creation">
|
||||||
|
{hasYoutubeChannels && !addingNewChannel ? (
|
||||||
|
<YoutubeTransferStatus alwaysShow addNewChannel={handleNewChannel} />
|
||||||
|
) : (
|
||||||
|
<Card
|
||||||
|
title={__('Connect with your fans while earning rewards')}
|
||||||
|
subtitle={__('Get your YouTube videos in front of the %site_name% audience.', {
|
||||||
|
site_name: IS_WEB ? 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 Channel')}
|
||||||
|
</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
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
I want to sync my content to 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')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{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>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
}
|
|
@ -40,6 +40,7 @@
|
||||||
@import 'component/pagination';
|
@import 'component/pagination';
|
||||||
@import 'component/purchase';
|
@import 'component/purchase';
|
||||||
@import 'component/placeholder';
|
@import 'component/placeholder';
|
||||||
|
@import 'component/progress';
|
||||||
@import 'component/search';
|
@import 'component/search';
|
||||||
@import 'component/claim-search';
|
@import 'component/claim-search';
|
||||||
@import 'component/section';
|
@import 'component/section';
|
||||||
|
|
|
@ -388,7 +388,10 @@ fieldset-group {
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-field--short {
|
.form-field--short {
|
||||||
width: 25em;
|
width: 100%;
|
||||||
|
@media (min-width: $breakpoint-small) {
|
||||||
|
width: 25em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-field--price-amount {
|
.form-field--price-amount {
|
||||||
|
|
|
@ -186,3 +186,9 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header__auth-title {
|
||||||
|
@media (min-width: $breakpoint-small) {
|
||||||
|
font-size: var(--font-large);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
17
ui/scss/component/_progress.scss
Normal file
17
ui/scss/component/_progress.scss
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
.progress__item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:not(:first-of-type) {
|
||||||
|
margin-top: var(--spacing-s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress__complete-icon {
|
||||||
|
margin-left: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress__complete-icon--completed {
|
||||||
|
@extend .progress__complete-icon;
|
||||||
|
stroke: var(--color-primary);
|
||||||
|
}
|
Loading…
Reference in a new issue