add yotube sync to initial sign up flow

This commit is contained in:
Sean Yesmunt 2020-09-04 16:11:28 -04:00
parent baafd60f4f
commit f547053ebc
11 changed files with 125 additions and 56 deletions

View file

@ -23,6 +23,8 @@ type Props = {
setShareDiagnosticData: boolean => void, setShareDiagnosticData: boolean => void,
doSignUp: (string, ?string) => Promise<any>, doSignUp: (string, ?string) => Promise<any>,
clearEmailEntry: () => void, clearEmailEntry: () => void,
interestedInYoutubSync: boolean,
doToggleInterestedInYoutubeSync: () => void,
}; };
function UserEmailNew(props: Props) { function UserEmailNew(props: Props) {
@ -35,6 +37,8 @@ function UserEmailNew(props: Props) {
setShareDiagnosticData, setShareDiagnosticData,
clearEmailEntry, clearEmailEntry,
emailExists, emailExists,
interestedInYoutubSync,
doToggleInterestedInYoutubeSync,
} = props; } = props;
const { share_usage_data: shareUsageData } = daemonSettings; const { share_usage_data: shareUsageData } = daemonSettings;
const { push, location } = useHistory(); const { push, location } = useHistory();
@ -113,6 +117,14 @@ function UserEmailNew(props: Props) {
onChange={e => setPassword(e.target.value)} onChange={e => setPassword(e.target.value)}
/> />
<FormField
type="checkbox"
name="youtube_sync_checkbox"
label={__('Sync my YouTube channel')}
checked={interestedInYoutubSync}
onChange={() => doToggleInterestedInYoutubeSync()}
/>
{!IS_WEB && ( {!IS_WEB && (
<FormField <FormField
type="checkbox" type="checkbox"

View file

@ -1,5 +1,4 @@
// @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';
@ -15,10 +14,18 @@ type Props = {
createChannelError: string, createChannelError: string,
claimingReward: boolean, claimingReward: boolean,
user: User, user: User,
doToggleInterestedInYoutubeSync: () => void,
}; };
function UserFirstChannel(props: Props) { function UserFirstChannel(props: Props) {
const { createChannel, creatingChannel, claimingReward, user, createChannelError } = props; const {
createChannel,
creatingChannel,
claimingReward,
user,
createChannelError,
doToggleInterestedInYoutubeSync,
} = props;
const { primary_email: primaryEmail } = user; const { primary_email: primaryEmail } = user;
const initialChannel = primaryEmail ? primaryEmail.split('@')[0] : ''; const initialChannel = primaryEmail ? primaryEmail.split('@')[0] : '';
const [channel, setChannel] = useState(initialChannel); const [channel, setChannel] = useState(initialChannel);
@ -87,7 +94,7 @@ function UserFirstChannel(props: Props) {
<Button <Button
button="link" button="link"
label={__('Sync it and skip this step')} label={__('Sync it and skip this step')}
navigate={`/$/${PAGES.YOUTUBE_SYNC}`} onClick={() => doToggleInterestedInYoutubeSync()}
/> />
), ),
}} }}

View file

@ -20,6 +20,8 @@ import {
SETTINGS, SETTINGS,
} from 'lbry-redux'; } from 'lbry-redux';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
import { selectInterestedInYoutubeSync } from 'redux/selectors/app';
import { doToggleInterestedInYoutubeSync } from 'redux/actions/app';
import UserSignIn from './view'; import UserSignIn from './view';
const select = state => ({ const select = state => ({
@ -42,6 +44,7 @@ const select = state => ({
syncingWallet: selectGetSyncIsPending(state), syncingWallet: selectGetSyncIsPending(state),
hasSynced: Boolean(selectSyncHash(state)), hasSynced: Boolean(selectSyncHash(state)),
creatingChannel: selectCreatingChannel(state), creatingChannel: selectCreatingChannel(state),
interestedInYoutubeSync: selectInterestedInYoutubeSync(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
@ -60,6 +63,7 @@ const perform = dispatch => ({
), ),
syncSettings: () => dispatch(doSyncClientSettings()), syncSettings: () => dispatch(doSyncClientSettings()),
setClientSetting: (setting, value) => dispatch(doSetClientSetting(setting, value)), setClientSetting: (setting, value) => dispatch(doSetClientSetting(setting, value)),
doToggleInterestedInYoutubeSync: () => dispatch(doToggleInterestedInYoutubeSync()),
}); });
export default connect(select, perform)(UserSignIn); export default connect(select, perform)(UserSignIn);

View file

@ -2,12 +2,13 @@
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { withRouter } from 'react-router'; import { useHistory } from 'react-router';
import UserEmailNew from 'component/userEmailNew'; import UserEmailNew from 'component/userEmailNew';
import UserEmailVerify from 'component/userEmailVerify'; import UserEmailVerify from 'component/userEmailVerify';
import UserFirstChannel from 'component/userFirstChannel'; import UserFirstChannel from 'component/userFirstChannel';
import UserChannelFollowIntro from 'component/userChannelFollowIntro'; import UserChannelFollowIntro from 'component/userChannelFollowIntro';
import UserTagFollowIntro from 'component/userTagFollowIntro'; import UserTagFollowIntro from 'component/userTagFollowIntro';
import YoutubeSync from 'page/youtubeSync';
import { DEFAULT_BID_FOR_FIRST_CHANNEL } from 'component/userFirstChannel/view'; import { DEFAULT_BID_FOR_FIRST_CHANNEL } from 'component/userFirstChannel/view';
import { YOUTUBE_STATUSES } from 'lbryinc'; import { YOUTUBE_STATUSES } from 'lbryinc';
import { SETTINGS } from 'lbry-redux'; import { SETTINGS } from 'lbry-redux';
@ -18,6 +19,10 @@ import YoutubeTransferStatus from 'component/youtubeTransferStatus';
import useFetched from 'effects/use-fetched'; import useFetched from 'effects/use-fetched';
import Confetti from 'react-confetti'; import Confetti from 'react-confetti';
const REDIRECT_PARAM = 'redirect';
const REDIRECT_IMMEDIATELY_PARAM = 'immediate';
const STEP_PARAM = 'step';
type Props = { type Props = {
user: ?User, user: ?User,
emailToVerify: ?string, emailToVerify: ?string,
@ -29,8 +34,6 @@ type Props = {
claimNewUserReward: () => void, claimNewUserReward: () => void,
fetchUser: () => void, fetchUser: () => void,
claimedRewards: Array<Reward>, claimedRewards: Array<Reward>,
history: { replace: string => void },
location: { search: string },
youtubeChannels: Array<any>, youtubeChannels: Array<any>,
syncEnabled: boolean, syncEnabled: boolean,
hasSynced: boolean, hasSynced: boolean,
@ -41,6 +44,8 @@ type Props = {
followingAcknowledged: boolean, followingAcknowledged: boolean,
tagsAcknowledged: boolean, tagsAcknowledged: boolean,
rewardsAcknowledged: boolean, rewardsAcknowledged: boolean,
interestedInYoutubeSync: boolean,
doToggleInterestedInYoutubeSync: () => void,
}; };
function UserSignUp(props: Props) { function UserSignUp(props: Props) {
@ -53,8 +58,6 @@ function UserSignUp(props: Props) {
claimConfirmEmailReward, claimConfirmEmailReward,
claimNewUserReward, claimNewUserReward,
balance, balance,
history,
location,
fetchUser, fetchUser,
youtubeChannels, youtubeChannels,
syncEnabled, syncEnabled,
@ -67,12 +70,17 @@ function UserSignUp(props: Props) {
rewardsAcknowledged, rewardsAcknowledged,
syncSettings, syncSettings,
setClientSetting, setClientSetting,
interestedInYoutubeSync,
doToggleInterestedInYoutubeSync,
} = props; } = props;
const { search } = location; const {
location: { search, pathname },
replace,
} = useHistory();
const urlParams = new URLSearchParams(search); const urlParams = new URLSearchParams(search);
const redirect = urlParams.get('redirect'); const redirect = urlParams.get(REDIRECT_PARAM);
const step = urlParams.get('step'); const step = urlParams.get(STEP_PARAM);
const shouldRedirectImmediately = urlParams.get('immediate'); const shouldRedirectImmediately = urlParams.get(REDIRECT_IMMEDIATELY_PARAM);
const [initialSignInStep, setInitialSignInStep] = React.useState(); const [initialSignInStep, setInitialSignInStep] = React.useState();
const hasVerifiedEmail = user && user.has_verified_email; const hasVerifiedEmail = user && user.has_verified_email;
const rewardsApproved = user && user.is_reward_approved; const rewardsApproved = user && user.is_reward_approved;
@ -97,11 +105,12 @@ function UserSignUp(props: Props) {
const showSyncPassword = syncEnabled && getSyncError; const showSyncPassword = syncEnabled && getSyncError;
const showChannelCreation = const showChannelCreation =
hasVerifiedEmail && hasVerifiedEmail &&
balance !== undefined && ((balance !== undefined &&
balance !== null && balance !== null &&
balance > DEFAULT_BID_FOR_FIRST_CHANNEL && balance > DEFAULT_BID_FOR_FIRST_CHANNEL &&
channelCount === 0 && channelCount === 0 &&
!hasYoutubeChannels; !hasYoutubeChannels) ||
interestedInYoutubeSync);
const showYoutubeTransfer = hasVerifiedEmail && hasYoutubeChannels && !isYoutubeTransferComplete; const showYoutubeTransfer = hasVerifiedEmail && hasYoutubeChannels && !isYoutubeTransferComplete;
const showFollowIntro = step === 'channels' || (hasVerifiedEmail && !followingAcknowledged); const showFollowIntro = step === 'channels' || (hasVerifiedEmail && !followingAcknowledged);
const showTagsIntro = step === 'tags' || (hasVerifiedEmail && !tagsAcknowledged); const showTagsIntro = step === 'tags' || (hasVerifiedEmail && !tagsAcknowledged);
@ -150,7 +159,12 @@ function UserSignUp(props: Props) {
// Loop through this list from the end, until it finds a matching component // Loop through this list from the end, until it finds a matching component
// If it never finds one, assume the user has completed every step and redirect them // If it never finds one, assume the user has completed every step and redirect them
const SIGN_IN_FLOW = [ const SIGN_IN_FLOW = [
showEmail && <UserEmailNew />, showEmail && (
<UserEmailNew
interestedInYoutubSync={interestedInYoutubeSync}
doToggleInterestedInYoutubeSync={doToggleInterestedInYoutubeSync}
/>
),
showEmailVerification && <UserEmailVerify />, showEmailVerification && <UserEmailVerify />,
showUserVerification && ( showUserVerification && (
<UserVerify <UserVerify
@ -159,47 +173,48 @@ function UserSignUp(props: Props) {
}} }}
/> />
), ),
showChannelCreation && <UserFirstChannel />, showChannelCreation &&
(interestedInYoutubeSync ? (
<YoutubeSync inSignUpFlow />
) : (
<UserFirstChannel doToggleInterestedInYoutubeSync={doToggleInterestedInYoutubeSync} />
)),
showFollowIntro && ( showFollowIntro && (
<UserChannelFollowIntro <UserChannelFollowIntro
onContinue={() => { onContinue={() => {
let url = `/$/${PAGES.AUTH}?reset_scroll=1`; if (urlParams.get('reset_scroll')) {
if (redirect) { urlParams.delete('reset_scroll');
url += `&redirect=${redirect}`; urlParams.append('reset_scroll', '2');
}
if (shouldRedirectImmediately) {
url += `&immediate=true`;
} }
history.replace(url); urlParams.delete(STEP_PARAM);
setSettingAndSync(SETTINGS.FOLLOWING_ACKNOWLEDGED, true); setSettingAndSync(SETTINGS.FOLLOWING_ACKNOWLEDGED, true);
replace(`${pathname}?${urlParams.toString()}`);
}} }}
onBack={() => { onBack={() => {
let url = `/$/${PAGES.AUTH}?reset_scroll=1&step=tags`; if (urlParams.get('reset_scroll')) {
if (redirect) { urlParams.delete('reset_scroll');
url += `&redirect=${redirect}`; urlParams.append('reset_scroll', '3');
}
if (shouldRedirectImmediately) {
url += `&immediate=true`;
} }
history.replace(url);
setSettingAndSync(SETTINGS.FOLLOWING_ACKNOWLEDGED, false); setSettingAndSync(SETTINGS.FOLLOWING_ACKNOWLEDGED, false);
replace(`${pathname}?${urlParams.toString()}`);
}} }}
/> />
), ),
showTagsIntro && ( showTagsIntro && (
<UserTagFollowIntro <UserTagFollowIntro
onContinue={() => { onContinue={() => {
let url = `/$/${PAGES.AUTH}?reset_scroll=1&step=channels`; let url = `/$/${PAGES.AUTH}?reset_scroll=1&${STEP_PARAM}=channels`;
if (redirect) { if (redirect) {
url += `&redirect=${redirect}`; url += `&${REDIRECT_PARAM}=${redirect}`;
} }
if (shouldRedirectImmediately) { if (shouldRedirectImmediately) {
url += `&immediate=true`; url += `&${REDIRECT_IMMEDIATELY_PARAM}=true`;
} }
history.replace(url); replace(url);
setSettingAndSync(SETTINGS.TAGS_ACKNOWLEDGED, true); setSettingAndSync(SETTINGS.TAGS_ACKNOWLEDGED, true);
}} }}
/> />
@ -228,7 +243,7 @@ function UserSignUp(props: Props) {
if (!initialSignInStep) { if (!initialSignInStep) {
setInitialSignInStep(i); setInitialSignInStep(i);
} else if (i !== initialSignInStep && i !== SIGN_IN_FLOW.length - 1) { } else if (i !== initialSignInStep && i !== SIGN_IN_FLOW.length - 1) {
history.replace(redirect); replace(redirect);
} }
} }
@ -250,7 +265,7 @@ function UserSignUp(props: Props) {
}, [componentToRender, claimNewUserReward]); }, [componentToRender, claimNewUserReward]);
if (!componentToRender) { if (!componentToRender) {
history.replace(redirect || '/'); replace(redirect || '/');
} }
return ( return (
@ -258,4 +273,4 @@ function UserSignUp(props: Props) {
); );
} }
export default withRouter(UserSignUp); export default UserSignUp;

View file

@ -1,4 +1,5 @@
// @flow // @flow
import { SITE_NAME } from 'config';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import * as React from 'react'; import * as React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
@ -168,20 +169,15 @@ export default function YoutubeTransferStatus(props: Props) {
onClick={claimChannels} onClick={claimChannels}
label={youtubeChannels.length > 1 ? __('Claim Channels') : __('Claim Channel')} label={youtubeChannels.length > 1 ? __('Claim Channels') : __('Claim Channel')}
/> />
{addNewChannel ? ( <Button button="link" label={__('Explore %SITE_NAME%', { SITE_NAME })} navigate="/" />
<Button button="link" label={__('Add Another Channel')} onClick={addNewChannel} />
) : (
<Button button="link" label={__('Learn More')} href="https://lbry.com/faq/youtube#transfer" />
)}
</div> </div>
<p className="help"> <p className="help">
{youtubeChannels.length > 1 {youtubeChannels.length > 1
? __('You will be able to claim your channels once they finish syncing.') ? __('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.')}{' '} : __('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" />{' '}
<Button button="link" label={__('Learn More')} href="https://lbry.com/faq/youtube#transfer" /> {addNewChannel && <Button button="link" label={__('Add Another Channel')} onClick={addNewChannel} />}
)}
</p> </p>
</> </>
} }

View file

@ -28,6 +28,7 @@ export const SET_WELCOME_VERSION = 'SET_WELCOME_VERSION';
export const SET_ALLOW_ANALYTICS = 'SET_ALLOW_ANALYTICS'; export const SET_ALLOW_ANALYTICS = 'SET_ALLOW_ANALYTICS';
export const SET_HAS_NAVIGATED = 'SET_HAS_NAVIGATED'; export const SET_HAS_NAVIGATED = 'SET_HAS_NAVIGATED';
export const SET_SYNC_LOCK = 'SET_SYNC_LOCK'; export const SET_SYNC_LOCK = 'SET_SYNC_LOCK';
export const TOGGLE_YOUTUBE_SYNC_INTEREST = 'TOGGLE_YOUTUBE_SYNC_INTEREST';
// Navigation // Navigation
export const CHANGE_AFTER_AUTH_PATH = 'CHANGE_AFTER_AUTH_PATH'; export const CHANGE_AFTER_AUTH_PATH = 'CHANGE_AFTER_AUTH_PATH';

View file

@ -21,10 +21,11 @@ const NEW_CHANNEL_PARAM = 'new_channel';
type Props = { type Props = {
youtubeChannels: ?Array<{ transfer_state: string, sync_status: string }>, youtubeChannels: ?Array<{ transfer_state: string, sync_status: string }>,
doUserFetch: () => void, doUserFetch: () => void,
inSignUpFlow?: boolean,
}; };
export default function YoutubeSync(props: Props) { export default function YoutubeSync(props: Props) {
const { youtubeChannels, doUserFetch } = props; const { youtubeChannels, doUserFetch, inSignUpFlow = false } = props;
const { const {
location: { search, pathname }, location: { search, pathname },
push, push,
@ -56,7 +57,7 @@ export default function YoutubeSync(props: Props) {
type: 'sync', type: 'sync',
immediate_sync: true, immediate_sync: true,
desired_lbry_channel_name: `@${channel}`, desired_lbry_channel_name: `@${channel}`,
return_url: `https://${DOMAIN}/$/${PAGES.YOUTUBE_SYNC}`, return_url: `https://${DOMAIN}/$/${inSignUpFlow ? PAGES.AUTH : PAGES.YOUTUBE_SYNC}`,
}).then(ytAuthUrl => { }).then(ytAuthUrl => {
// react-router isn't needed since it's a different domain // react-router isn't needed since it's a different domain
window.location.href = ytAuthUrl; window.location.href = ytAuthUrl;
@ -79,14 +80,24 @@ export default function YoutubeSync(props: Props) {
setAddingNewChannel(true); setAddingNewChannel(true);
} }
const Wrapper = (props: { children: any }) => {
return inSignUpFlow ? (
<>{props.children}</>
) : (
<Page noSideNavigation authPage>
{props.children}
</Page>
);
};
return ( return (
<Page noSideNavigation authPage> <Wrapper>
<div className="main__channel-creation"> <div className="main__channel-creation">
{hasYoutubeChannels && !addingNewChannel ? ( {hasYoutubeChannels && !addingNewChannel ? (
<YoutubeTransferStatus alwaysShow addNewChannel={handleNewChannel} /> <YoutubeTransferStatus alwaysShow addNewChannel={handleNewChannel} />
) : ( ) : (
<Card <Card
title={__('Connect with your fans while earning rewards')} title={__('Sync your YouTube channel to %site_name%', { site_name: IS_WEB ? SITE_NAME : 'LBRY' })}
subtitle={__('Get your YouTube videos in front of the %site_name% audience.', { subtitle={__('Get your YouTube videos in front of the %site_name% audience.', {
site_name: IS_WEB ? SITE_NAME : 'LBRY', site_name: IS_WEB ? SITE_NAME : 'LBRY',
})} })}
@ -95,7 +106,11 @@ export default function YoutubeSync(props: Props) {
<fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix"> <fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix">
<fieldset-section> <fieldset-section>
<label htmlFor="auth_first_channel"> <label htmlFor="auth_first_channel">
{nameError ? <span className="error__text">{nameError}</span> : __('Your Channel')} {nameError ? (
<span className="error__text">{nameError}</span>
) : (
__('Your %site_name% channel name', { site_name: IS_WEB ? SITE_NAME : 'LBRY' })
)}
</label> </label>
<div className="form-field__prefix">@</div> <div className="form-field__prefix">@</div>
</fieldset-section> </fieldset-section>
@ -128,10 +143,11 @@ export default function YoutubeSync(props: Props) {
href="https://lbry.com/faq/youtube" href="https://lbry.com/faq/youtube"
/> />
), ),
site_name: SITE_NAME,
}} }}
> >
I want to sync my content to the LBRY network and agree to %terms%. I have also read and I want to sync my content to %site_name% and the LBRY network and agree to %terms%. I have also
understand %faq%. read and understand %faq%.
</I18nMessage> </I18nMessage>
} }
/> />
@ -163,6 +179,6 @@ export default function YoutubeSync(props: Props) {
/> />
)} )}
</div> </div>
</Page> </Wrapper>
); );
} }

View file

@ -667,3 +667,9 @@ export function doHandleSyncComplete(error, hasNewData) {
export function doSyncWithPreferences() { export function doSyncWithPreferences() {
return dispatch => dispatch(doSyncSubscribe()); return dispatch => dispatch(doSyncSubscribe());
} }
export function doToggleInterestedInYoutubeSync() {
return {
type: ACTIONS.TOGGLE_YOUTUBE_SYNC_INTEREST,
};
}

View file

@ -43,6 +43,7 @@ export type AppState = {
allowAnalytics: boolean, allowAnalytics: boolean,
hasNavigated: boolean, hasNavigated: boolean,
syncLocked: boolean, syncLocked: boolean,
interestedInYoutubeSync: boolean,
}; };
const defaultState: AppState = { const defaultState: AppState = {
@ -78,6 +79,7 @@ const defaultState: AppState = {
allowAnalytics: false, allowAnalytics: false,
hasNavigated: false, hasNavigated: false,
syncLocked: false, syncLocked: false,
interestedInYoutubeSync: false,
}; };
// @@router comes from react-router // @@router comes from react-router
@ -289,6 +291,13 @@ reducers[ACTIONS.TOGGLE_SEARCH_EXPANDED] = state =>
searchOptionsExpanded: !state.searchOptionsExpanded, searchOptionsExpanded: !state.searchOptionsExpanded,
}); });
reducers[ACTIONS.TOGGLE_YOUTUBE_SYNC_INTEREST] = (state, action) => {
return {
...state,
interestedInYoutubeSync: !state.interestedInYoutubeSync,
};
};
reducers[LBRY_REDUX_ACTIONS.USER_STATE_POPULATE] = (state, action) => { reducers[LBRY_REDUX_ACTIONS.USER_STATE_POPULATE] = (state, action) => {
const { welcomeVersion, allowAnalytics } = action.data; const { welcomeVersion, allowAnalytics } = action.data;
return { return {

View file

@ -82,3 +82,5 @@ export const selectScrollStartingPosition = createSelector(selectState, state =>
export const selectIsPasswordSaved = createSelector(selectState, state => state.isPasswordSaved); export const selectIsPasswordSaved = createSelector(selectState, state => state.isPasswordSaved);
export const selectSyncIsLocked = createSelector(selectState, state => state.syncLocked); export const selectSyncIsLocked = createSelector(selectState, state => state.syncLocked);
export const selectInterestedInYoutubeSync = createSelector(selectState, state => state.interestedInYoutubeSync);

View file

@ -56,6 +56,7 @@ const appFilter = createFilter('app', [
'muted', 'muted',
'allowAnalytics', 'allowAnalytics',
'welcomeVersion', 'welcomeVersion',
'interestedInYoutubeSync',
]); ]);
// We only need to persist the receiveAddress for the wallet // We only need to persist the receiveAddress for the wallet
const walletFilter = createFilter('wallet', ['receiveAddress']); const walletFilter = createFilter('wallet', ['receiveAddress']);