2020-04-13 21:16:07 +02:00
|
|
|
// @flow
|
|
|
|
import * as PAGES from 'constants/pages';
|
|
|
|
import React from 'react';
|
|
|
|
import { withRouter } from 'react-router';
|
|
|
|
import UserEmailNew from 'component/userEmailNew';
|
|
|
|
import UserEmailVerify from 'component/userEmailVerify';
|
|
|
|
import UserFirstChannel from 'component/userFirstChannel';
|
|
|
|
import UserChannelFollowIntro from 'component/userChannelFollowIntro';
|
|
|
|
import UserTagFollowIntro from 'component/userTagFollowIntro';
|
|
|
|
import { DEFAULT_BID_FOR_FIRST_CHANNEL } from 'component/userFirstChannel/view';
|
|
|
|
import { rewards as REWARDS, YOUTUBE_STATUSES } from 'lbryinc';
|
|
|
|
import UserVerify from 'component/userVerify';
|
|
|
|
import Spinner from 'component/spinner';
|
|
|
|
import YoutubeTransferStatus from 'component/youtubeTransferStatus';
|
|
|
|
import SyncPassword from 'component/syncPassword';
|
|
|
|
import useFetched from 'effects/use-fetched';
|
|
|
|
import usePersistedState from 'effects/use-persisted-state';
|
|
|
|
import Confetti from 'react-confetti';
|
|
|
|
|
|
|
|
type Props = {
|
|
|
|
user: ?User,
|
|
|
|
emailToVerify: ?string,
|
|
|
|
channels: ?Array<string>,
|
|
|
|
balance: ?number,
|
|
|
|
fetchingChannels: boolean,
|
|
|
|
claimingReward: boolean,
|
|
|
|
claimReward: () => void,
|
|
|
|
fetchUser: () => void,
|
|
|
|
claimedRewards: Array<Reward>,
|
|
|
|
history: { replace: string => void },
|
|
|
|
location: { search: string },
|
|
|
|
youtubeChannels: Array<any>,
|
|
|
|
syncEnabled: boolean,
|
|
|
|
hasSynced: boolean,
|
|
|
|
syncingWallet: boolean,
|
|
|
|
getSyncError: ?string,
|
|
|
|
creatingChannel: boolean,
|
|
|
|
};
|
|
|
|
|
|
|
|
function UserSignIn(props: Props) {
|
|
|
|
const {
|
|
|
|
emailToVerify,
|
|
|
|
user,
|
|
|
|
claimingReward,
|
|
|
|
claimedRewards,
|
|
|
|
channels,
|
|
|
|
claimReward,
|
|
|
|
balance,
|
|
|
|
history,
|
|
|
|
location,
|
|
|
|
fetchUser,
|
|
|
|
youtubeChannels,
|
|
|
|
syncEnabled,
|
|
|
|
syncingWallet,
|
|
|
|
getSyncError,
|
|
|
|
hasSynced,
|
|
|
|
fetchingChannels,
|
|
|
|
creatingChannel,
|
|
|
|
} = props;
|
|
|
|
const { search } = location;
|
|
|
|
const urlParams = new URLSearchParams(search);
|
|
|
|
const redirect = urlParams.get('redirect');
|
|
|
|
const step = urlParams.get('step');
|
|
|
|
const shouldRedirectImmediately = urlParams.get('immediate');
|
|
|
|
const [initialSignInStep, setInitialSignInStep] = React.useState();
|
|
|
|
const [hasSeenFollowList, setHasSeenFollowList] = usePersistedState('channel-follow-intro', false);
|
|
|
|
const [hasSkippedRewards, setHasSkippedRewards] = usePersistedState('skip-rewards-intro', false);
|
|
|
|
const [hasSeenTagsList, setHasSeenTagsList] = usePersistedState('channel-follow-intro', false);
|
|
|
|
const hasVerifiedEmail = user && user.has_verified_email;
|
|
|
|
const rewardsApproved = user && user.is_reward_approved;
|
|
|
|
const isIdentityVerified = user && user.is_identity_verified;
|
2020-04-20 22:53:40 +02:00
|
|
|
const passwordSet = user && user.password_set;
|
2020-04-13 21:16:07 +02:00
|
|
|
const hasFetchedReward = useFetched(claimingReward);
|
|
|
|
const channelCount = channels ? channels.length : 0;
|
|
|
|
const hasClaimedEmailAward = claimedRewards.some(reward => reward.reward_type === REWARDS.TYPE_CONFIRM_EMAIL);
|
|
|
|
const hasYoutubeChannels = youtubeChannels && Boolean(youtubeChannels.length);
|
|
|
|
const isYoutubeTransferComplete =
|
|
|
|
hasYoutubeChannels &&
|
|
|
|
youtubeChannels.every(channel => channel.transfer_state === YOUTUBE_STATUSES.COMPLETED_TRANSFER);
|
|
|
|
|
|
|
|
// Complexity warning
|
|
|
|
// We can't just check if we are currently fetching something
|
|
|
|
// We may want to keep a component rendered while something is being fetched, instead of replacing it with the large spinner
|
|
|
|
// The verbose variable names are an attempt to alleviate _some_ of the confusion from handling all edge cases that come from
|
|
|
|
// reward claiming, channel creation, account syncing, and youtube transfer
|
|
|
|
// The possible screens for the sign in flow
|
|
|
|
const showEmail = !hasVerifiedEmail;
|
2020-04-20 22:53:40 +02:00
|
|
|
const showEmailVerification = (emailToVerify && !hasVerifiedEmail) || (!hasVerifiedEmail && passwordSet);
|
2020-04-13 21:16:07 +02:00
|
|
|
const showUserVerification = hasVerifiedEmail && !rewardsApproved && !isIdentityVerified && !hasSkippedRewards;
|
|
|
|
const showSyncPassword = syncEnabled && getSyncError;
|
|
|
|
const showChannelCreation =
|
|
|
|
hasVerifiedEmail &&
|
|
|
|
balance !== undefined &&
|
|
|
|
balance !== null &&
|
|
|
|
balance > DEFAULT_BID_FOR_FIRST_CHANNEL &&
|
|
|
|
channelCount === 0 &&
|
|
|
|
!hasYoutubeChannels;
|
|
|
|
const showYoutubeTransfer = hasVerifiedEmail && hasYoutubeChannels && !isYoutubeTransferComplete;
|
|
|
|
const showFollowIntro = step === 'channels' || (hasVerifiedEmail && !hasSeenFollowList);
|
|
|
|
const showTagsIntro = step === 'tags' || (hasVerifiedEmail && !hasSeenTagsList);
|
|
|
|
const canHijackSignInFlowWithSpinner = hasVerifiedEmail && !getSyncError && !showFollowIntro;
|
|
|
|
const isCurrentlyFetchingSomething = fetchingChannels || claimingReward || syncingWallet || creatingChannel;
|
|
|
|
const isWaitingForSomethingToFinish =
|
|
|
|
// If the user has claimed the email award, we need to wait until the balance updates sometime in the future
|
|
|
|
(!hasFetchedReward && !hasClaimedEmailAward) || (syncEnabled && !hasSynced);
|
|
|
|
const showLoadingSpinner =
|
|
|
|
canHijackSignInFlowWithSpinner && (isCurrentlyFetchingSomething || isWaitingForSomethingToFinish);
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
fetchUser();
|
|
|
|
}, [fetchUser]);
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
// Don't claim the reward if sync is enabled until after a sync has been completed successfully
|
|
|
|
// If we do it before, we could end up trying to sync a wallet with a non-zero balance which will fail to sync
|
|
|
|
const delayForSync = syncEnabled && !hasSynced;
|
|
|
|
|
|
|
|
if (hasVerifiedEmail && !hasClaimedEmailAward && !hasFetchedReward && !delayForSync) {
|
|
|
|
claimReward();
|
|
|
|
}
|
|
|
|
}, [hasVerifiedEmail, claimReward, hasClaimedEmailAward, hasFetchedReward, syncEnabled, hasSynced, balance]);
|
|
|
|
|
|
|
|
// 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
|
|
|
|
const SIGN_IN_FLOW = [
|
|
|
|
showEmail && <UserEmailNew />,
|
|
|
|
showEmailVerification && <UserEmailVerify />,
|
|
|
|
showUserVerification && <UserVerify onSkip={() => setHasSkippedRewards(true)} />,
|
|
|
|
showChannelCreation && <UserFirstChannel />,
|
|
|
|
showFollowIntro && (
|
|
|
|
<UserChannelFollowIntro
|
|
|
|
onContinue={() => {
|
|
|
|
let url = `/$/${PAGES.AUTH}?reset_scroll=1`;
|
|
|
|
if (redirect) {
|
|
|
|
url += `&redirect=${redirect}`;
|
|
|
|
}
|
|
|
|
if (shouldRedirectImmediately) {
|
|
|
|
url += `&immediate=true`;
|
|
|
|
}
|
|
|
|
|
|
|
|
history.replace(url);
|
|
|
|
setHasSeenFollowList(true);
|
|
|
|
}}
|
|
|
|
onBack={() => {
|
|
|
|
let url = `/$/${PAGES.AUTH}?reset_scroll=1&step=tags`;
|
|
|
|
if (redirect) {
|
|
|
|
url += `&redirect=${redirect}`;
|
|
|
|
}
|
|
|
|
if (shouldRedirectImmediately) {
|
|
|
|
url += `&immediate=true`;
|
|
|
|
}
|
|
|
|
|
|
|
|
history.replace(url);
|
|
|
|
setHasSeenFollowList(false);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
),
|
|
|
|
showTagsIntro && (
|
|
|
|
<UserTagFollowIntro
|
|
|
|
onContinue={() => {
|
|
|
|
let url = `/$/${PAGES.AUTH}?reset_scroll=1&step=channels`;
|
|
|
|
if (redirect) {
|
|
|
|
url += `&redirect=${redirect}`;
|
|
|
|
}
|
|
|
|
if (shouldRedirectImmediately) {
|
|
|
|
url += `&immediate=true`;
|
|
|
|
}
|
|
|
|
|
|
|
|
history.replace(url);
|
|
|
|
setHasSeenTagsList(true);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
),
|
|
|
|
showYoutubeTransfer && (
|
|
|
|
<div>
|
|
|
|
<YoutubeTransferStatus /> <Confetti recycle={false} style={{ position: 'fixed' }} />
|
|
|
|
</div>
|
|
|
|
),
|
|
|
|
showSyncPassword && <SyncPassword />,
|
|
|
|
showLoadingSpinner && (
|
|
|
|
<div className="main--empty">
|
|
|
|
<Spinner />
|
|
|
|
</div>
|
|
|
|
),
|
|
|
|
];
|
|
|
|
|
|
|
|
function getSignInStep() {
|
|
|
|
for (var i = SIGN_IN_FLOW.length - 1; i > -1; i--) {
|
|
|
|
const Component = SIGN_IN_FLOW[i];
|
|
|
|
if (Component) {
|
|
|
|
// If we want to redirect immediately,
|
|
|
|
// remember the first step so we can redirect once a new step has been reached
|
|
|
|
// Ignore the loading step
|
|
|
|
if (redirect && shouldRedirectImmediately) {
|
|
|
|
if (!initialSignInStep) {
|
|
|
|
setInitialSignInStep(i);
|
|
|
|
} else if (i !== initialSignInStep && i !== SIGN_IN_FLOW.length - 1) {
|
|
|
|
history.replace(redirect);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Component;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const componentToRender = getSignInStep();
|
|
|
|
|
|
|
|
if (!componentToRender) {
|
|
|
|
history.replace(redirect || '/');
|
|
|
|
}
|
|
|
|
|
|
|
|
return <section className="main--contained">{componentToRender}</section>;
|
|
|
|
}
|
|
|
|
|
|
|
|
export default withRouter(UserSignIn);
|