add youtube sync to first run

This commit is contained in:
Sean Yesmunt 2020-09-03 16:05:38 -04:00
parent dec63d7a2e
commit bc89d774ba
18 changed files with 348 additions and 56 deletions

View file

@ -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.",

View file

@ -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>
),
}; };

View file

@ -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}`}

View file

@ -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={{

View file

@ -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' */}

View file

@ -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>
} }
/> />

View file

@ -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 ? (
</div> <Button button="link" label={__('Add Another Channel')} onClick={addNewChannel} />
) : !hideChannelLink ? (
<div className="card__actions">
<Button button="primary" navigate={`/$/${PAGES.CHANNELS}`} label={__('View your channels')} />
</div>
) : ( ) : (
false <Button button="link" label={__('Learn More')} href="https://lbry.com/faq/youtube#transfer" />
) )}
</div>
<p className="help">
{youtubeChannels.length > 1
? __('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.')}{' '}
{addNewChannel && (
<Button button="link" label={__('Learn More')} href="https://lbry.com/faq/youtube#transfer" />
)}
</p>
</>
} }
/> />
) )

View file

@ -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';

View file

@ -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';

View file

@ -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 => ({

View file

@ -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} />}

View file

@ -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>

View 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);

View 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>
);
}

View file

@ -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';

View file

@ -388,8 +388,11 @@ fieldset-group {
} }
.form-field--short { .form-field--short {
width: 100%;
@media (min-width: $breakpoint-small) {
width: 25em; width: 25em;
} }
}
.form-field--price-amount { .form-field--price-amount {
width: 7em; width: 7em;

View file

@ -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);
}
}

View 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);
}