From 2677cd17d8dcc3c824320498991d31c8cd54c452 Mon Sep 17 00:00:00 2001
From: Sean Yesmunt
Date: Mon, 13 Apr 2020 15:16:07 -0400
Subject: [PATCH] new signin/signup (#3960)
* new signin/signup
* cleanup and password reset
* new flow working
* cleanup
* add 'autoComplete' props
* fix prop
* try to call email/confirm before resetting password
* Dont use password reset token for email confirmation.
* add password reset
* password manager improvements
* update lbryinc
* cleanup
* slightly improve close button on sign up page
* moar fixes
* fix password autofil
Co-authored-by: Mark Beamer Jr
---
.eslintrc.json | 1 +
package.json | 4 +-
static/app-strings.json | 5 +-
ui/component/cardVerify/view.jsx | 2 +-
ui/component/channelCreate/view.jsx | 2 +-
ui/component/common/card.jsx | 4 +
ui/component/common/error-text.jsx | 12 +-
.../common/form-components/form-field.jsx | 4 +-
ui/component/common/icon-custom.jsx | 5 +
ui/component/common/nag.jsx | 26 +-
ui/component/errorBoundary/view.jsx | 10 +-
ui/component/header/index.js | 12 +-
ui/component/header/view.jsx | 56 ++++-
ui/component/invited/view.jsx | 2 +-
ui/component/publishFormErrors/view.jsx | 2 +-
ui/component/router/view.jsx | 8 +-
ui/component/selectAsset/view.jsx | 2 +-
ui/component/settingAccountPassword/index.js | 22 ++
ui/component/settingAccountPassword/view.jsx | 88 +++++++
ui/component/sideNavigation/view.jsx | 7 +-
ui/component/splash/view.jsx | 3 +-
ui/component/supportsLiquidate/view.jsx | 2 +-
ui/component/userChannelFollowIntro/view.jsx | 82 +++----
ui/component/userEmail/index.js | 5 +-
ui/component/userEmailNew/index.js | 17 +-
ui/component/userEmailNew/view.jsx | 218 ++++++++++-------
ui/component/userEmailReturning/index.js | 22 ++
ui/component/userEmailReturning/view.jsx | 111 +++++++++
ui/component/userEmailVerify/index.js | 5 +-
ui/component/userEmailVerify/view.jsx | 79 +++---
ui/component/userFirstChannel/view.jsx | 83 ++++---
ui/component/userPasswordReset/index.js | 26 ++
ui/component/userPasswordReset/view.jsx | 107 +++++++++
ui/component/userPasswordSet/index.js | 16 ++
ui/component/userPasswordSet/view.jsx | 108 +++++++++
ui/component/userSignIn/index.js | 53 +---
ui/component/userSignIn/view.jsx | 227 +++---------------
ui/component/userSignInPassword/index.js | 22 ++
ui/component/userSignInPassword/view.jsx | 67 ++++++
ui/component/userSignOutButton/index.js | 14 +-
ui/component/userSignOutButton/view.jsx | 18 +-
ui/component/userSignUp/index.js | 52 ++++
ui/component/userSignUp/view.jsx | 215 +++++++++++++++++
ui/component/userTagFollowIntro/view.jsx | 65 ++---
ui/component/userVerify/view.jsx | 2 +-
ui/component/walletSend/view.jsx | 2 +-
ui/constants/icons.js | 1 +
ui/constants/pages.js | 4 +-
ui/modal/modalRemoveFile/view.jsx | 2 +-
ui/modal/modalRevokeClaim/view.jsx | 2 +-
ui/modal/modalWalletEncrypt/view.jsx | 2 +-
ui/modal/walletReceive/view.jsx | 11 -
ui/modal/walletSend/view.jsx | 10 -
ui/page/passwordSet/index.js | 3 +
ui/page/passwordSet/view.jsx | 12 +
ui/page/rewards/view.jsx | 2 +-
ui/page/settings/view.jsx | 4 +-
ui/page/signIn/index.js | 11 +-
ui/page/signInVerify/view.jsx | 80 +++---
ui/page/signUp/index.js | 3 +
ui/page/signUp/view.jsx | 12 +
ui/scss/component/_button.scss | 5 +
ui/scss/component/_card.scss | 36 ++-
ui/scss/component/_claim-list.scss | 5 +
ui/scss/component/_header.scss | 10 +
ui/scss/component/_main.scss | 18 +-
ui/scss/component/_modal.scss | 6 +-
ui/scss/component/nag.scss | 4 +
ui/scss/component/section.scss | 57 ++++-
ui/scss/init/_gui.scss | 6 +-
ui/scss/themes/light.scss | 2 +-
ui/util/saved-passwords.js | 3 +-
yarn.lock | 12 +-
73 files changed, 1566 insertions(+), 652 deletions(-)
create mode 100644 ui/component/settingAccountPassword/index.js
create mode 100644 ui/component/settingAccountPassword/view.jsx
create mode 100644 ui/component/userEmailReturning/index.js
create mode 100644 ui/component/userEmailReturning/view.jsx
create mode 100644 ui/component/userPasswordReset/index.js
create mode 100644 ui/component/userPasswordReset/view.jsx
create mode 100644 ui/component/userPasswordSet/index.js
create mode 100644 ui/component/userPasswordSet/view.jsx
create mode 100644 ui/component/userSignInPassword/index.js
create mode 100644 ui/component/userSignInPassword/view.jsx
create mode 100644 ui/component/userSignUp/index.js
create mode 100644 ui/component/userSignUp/view.jsx
delete mode 100644 ui/modal/walletReceive/view.jsx
delete mode 100644 ui/modal/walletSend/view.jsx
create mode 100644 ui/page/passwordSet/index.js
create mode 100644 ui/page/passwordSet/view.jsx
create mode 100644 ui/page/signUp/index.js
create mode 100644 ui/page/signUp/view.jsx
diff --git a/.eslintrc.json b/.eslintrc.json
index 8c1d7d658..50e4be5cb 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -30,6 +30,7 @@
"one-var": 0,
"prefer-promise-reject-errors": 0,
"react/jsx-indent": 0,
+ "react/jsx-no-comment-textnodes": 0,
"react-hooks/exhaustive-deps": "warn",
"react-hooks/rules-of-hooks": "error",
"react/no-unescaped-entities": 0,
diff --git a/package.json b/package.json
index 28cb90b9b..47eb26d92 100644
--- a/package.json
+++ b/package.json
@@ -68,7 +68,7 @@
"@babel/register": "^7.0.0",
"@exponent/electron-cookies": "^2.0.0",
"@hot-loader/react-dom": "^16.8",
- "@lbry/components": "^4.0.1",
+ "@lbry/components": "^4.1.2",
"@reach/menu-button": "0.7.4",
"@reach/rect": "^0.2.1",
"@reach/tabs": "^0.1.5",
@@ -131,7 +131,7 @@
"json-loader": "^0.5.4",
"lbry-format": "https://github.com/lbryio/lbry-format.git",
"lbry-redux": "lbryio/lbry-redux#1097a63d44a20b87e443fbaa48f95fe3ea5e3f70",
- "lbryinc": "lbryio/lbryinc#0addc624db54000b0447f4539f91f5758d26eef3",
+ "lbryinc": "lbryio/lbryinc#12aefaa14343d2f3eac01f2683701f58e53f1848",
"lint-staged": "^7.0.2",
"localforage": "^1.7.1",
"lodash-es": "^4.17.14",
diff --git a/static/app-strings.json b/static/app-strings.json
index be009501c..2981c0ccd 100644
--- a/static/app-strings.json
+++ b/static/app-strings.json
@@ -1116,5 +1116,8 @@
"Repost %count%": "Repost %count%",
"File Description": "File Description",
"View %count% reposts": "View %count% reposts",
- "Preparing your content": "Preparing your content"
+ "Preparing your content": "Preparing your content",
+ "Already have an account? %sign_in%": "Already have an account? %sign_in%",
+ "Sign in with a password (optional)": "Sign in with a password (optional)",
+ "Don't have an account? %sign_up%": "Don't have an account? %sign_up%"
}
diff --git a/ui/component/cardVerify/view.jsx b/ui/component/cardVerify/view.jsx
index 901ed4963..904973481 100644
--- a/ui/component/cardVerify/view.jsx
+++ b/ui/component/cardVerify/view.jsx
@@ -165,7 +165,7 @@ class CardVerify extends React.Component {
return (
{scriptFailedToLoad && (
-
There was an error connecting to Stripe. Please try again later.
+
There was an error connecting to Stripe. Please try again later.
)}
{
return (
+ }
+ nag={
+
+ {passwordResetError && {passwordResetError}} />}
+ {passwordResetSuccess && (
+
+ )}
+
+ }
+ />
+
+ );
+}
+
+export default UserPasswordReset;
diff --git a/ui/component/userPasswordSet/index.js b/ui/component/userPasswordSet/index.js
new file mode 100644
index 000000000..ead9767aa
--- /dev/null
+++ b/ui/component/userPasswordSet/index.js
@@ -0,0 +1,16 @@
+import { connect } from 'react-redux';
+import { doClearEmailEntry, doUserFetch } from 'lbryinc';
+import { doToast } from 'lbry-redux';
+import UserSignIn from './view';
+
+const select = state => ({
+ // passwordSetSuccess: selectPasswordSetSuccess(state),
+ // passwordSetIsPending: selectPasswordSetIsPending(state),
+ // passwordSetError: selectPasswordSetError(state),
+});
+
+export default connect(select, {
+ doToast,
+ doClearEmailEntry,
+ doUserFetch,
+})(UserSignIn);
diff --git a/ui/component/userPasswordSet/view.jsx b/ui/component/userPasswordSet/view.jsx
new file mode 100644
index 000000000..70838a524
--- /dev/null
+++ b/ui/component/userPasswordSet/view.jsx
@@ -0,0 +1,108 @@
+// @flow
+import * as PAGES from 'constants/pages';
+import React from 'react';
+import { Lbryio } from 'lbryinc';
+import { useHistory } from 'react-router';
+import Card from 'component/common/card';
+import { Form, FormField } from 'component/common/form';
+import ErrorText from 'component/common/error-text';
+import Button from 'component/button';
+import Nag from 'component/common/nag';
+import Spinner from 'component/spinner';
+
+type Props = {
+ user: ?User,
+ doClearEmailEntry: () => void,
+ doUserFetch: () => void,
+ doToast: ({ message: string }) => void,
+ history: { push: string => void },
+ location: { search: string },
+ passwordSetPending: boolean,
+ passwordSetError: ?string,
+};
+
+function UserPasswordReset(props: Props) {
+ const { doClearEmailEntry, doToast, doUserFetch } = props;
+ const { location, push } = useHistory();
+ const urlParams = new URLSearchParams(location.search);
+ const email = urlParams.get('email');
+ const authToken = urlParams.get('auth_token');
+ const verificationToken = urlParams.get('verification_token');
+ const [password, setPassword] = React.useState('');
+ const [error, setError] = React.useState();
+ const [loading, setLoading] = React.useState(false);
+
+ function handleSubmit() {
+ setLoading(true);
+
+ Lbryio.call('user_email', 'confirm', {
+ email: email,
+ verification_token: verificationToken,
+ })
+ .then(() =>
+ Lbryio.call(
+ 'user_password',
+ 'set',
+ {
+ auth_token: authToken,
+ new_password: password,
+ },
+ 'post'
+ )
+ )
+ .then(doUserFetch)
+ .then(() => {
+ setLoading(false);
+ doToast({
+ message: __('Password successfully changed!'),
+ });
+ push(`/`);
+ })
+ .catch(error => {
+ setLoading(false);
+ setError(error.message);
+ });
+ }
+
+ function handleRestart() {
+ doClearEmailEntry();
+ push(`/$/${PAGES.AUTH_SIGNIN}`);
+ }
+
+ return (
+
+
+
+ setPassword(e.target.value)}
+ />
+
+
+
+
+ {loading && }
+
+
+
+ }
+ nag={error && {error}} />}
+ />
+
+ );
+}
+
+export default UserPasswordReset;
diff --git a/ui/component/userSignIn/index.js b/ui/component/userSignIn/index.js
index 76be35dcf..e0a559098 100644
--- a/ui/component/userSignIn/index.js
+++ b/ui/component/userSignIn/index.js
@@ -1,55 +1,14 @@
-import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux';
-import {
- selectEmailToVerify,
- selectUser,
- selectAccessToken,
- makeSelectIsRewardClaimPending,
- selectClaimedRewards,
- rewards as REWARD_TYPES,
- doClaimRewardType,
- doUserFetch,
- selectUserIsPending,
- selectYoutubeChannels,
- selectGetSyncIsPending,
- selectGetSyncErrorMessage,
- selectSyncHash,
-} from 'lbryinc';
-import { selectMyChannelClaims, selectBalance, selectFetchingMyChannels, selectCreatingChannel } from 'lbry-redux';
-import { makeSelectClientSetting } from 'redux/selectors/settings';
+import { selectUser, selectUserIsPending, selectEmailToVerify, selectPasswordExists, doUserSignIn } from 'lbryinc';
import UserSignIn from './view';
const select = state => ({
- emailToVerify: selectEmailToVerify(state),
user: selectUser(state),
- accessToken: selectAccessToken(state),
- channels: selectMyChannelClaims(state),
- claimedRewards: selectClaimedRewards(state),
- claimingReward: makeSelectIsRewardClaimPending()(state, {
- reward_type: REWARD_TYPES.TYPE_CONFIRM_EMAIL,
- }),
- balance: selectBalance(state),
- fetchingChannels: selectFetchingMyChannels(state),
- youtubeChannels: selectYoutubeChannels(state),
userFetchPending: selectUserIsPending(state),
- syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
- syncingWallet: selectGetSyncIsPending(state),
- getSyncError: selectGetSyncErrorMessage(state),
- hasSynced: Boolean(selectSyncHash(state)),
- creatingChannel: selectCreatingChannel(state),
+ emailToVerify: selectEmailToVerify(state),
+ passwordExists: selectPasswordExists(state),
});
-const perform = dispatch => ({
- fetchUser: () => dispatch(doUserFetch()),
- claimReward: () =>
- dispatch(
- doClaimRewardType(REWARD_TYPES.TYPE_CONFIRM_EMAIL, {
- notifyError: false,
- })
- ),
-});
-
-export default connect(
- select,
- perform
-)(UserSignIn);
+export default connect(select, {
+ doUserSignIn,
+})(UserSignIn);
diff --git a/ui/component/userSignIn/view.jsx b/ui/component/userSignIn/view.jsx
index b0dc57ac9..e63b91849 100644
--- a/ui/component/userSignIn/view.jsx
+++ b/ui/component/userSignIn/view.jsx
@@ -1,215 +1,58 @@
// @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 UserEmailReturning from 'component/userEmailReturning';
+import UserSignInPassword from 'component/userSignInPassword';
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,
- balance: ?number,
- fetchingChannels: boolean,
- claimingReward: boolean,
- claimReward: () => void,
- fetchUser: () => void,
- claimedRewards: Array,
- history: { replace: string => void },
+ history: { push: string => void },
location: { search: string },
- youtubeChannels: Array,
- syncEnabled: boolean,
- hasSynced: boolean,
- syncingWallet: boolean,
- getSyncError: ?string,
- creatingChannel: boolean,
+ userFetchPending: boolean,
+ doUserSignIn: string => void,
+ emailToVerify: ?string,
+ passwordExists: 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 { user, location, history, doUserSignIn, userFetchPending, emailToVerify, passwordExists } = 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 [emailOnlyLogin, setEmailOnlyLogin] = React.useState(false);
const hasVerifiedEmail = user && user.has_verified_email;
- const rewardsApproved = user && user.is_reward_approved;
- const isIdentityVerified = user && user.is_identity_verified;
- 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 = !emailToVerify && !hasVerifiedEmail;
- const showEmailVerification = emailToVerify && !hasVerifiedEmail;
- 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);
+ const redirect = urlParams.get('redirect');
+ const showLoading = userFetchPending;
+ const showEmail = !passwordExists || emailOnlyLogin;
+ const showPassword = !showEmail && emailToVerify && passwordExists;
React.useEffect(() => {
- fetchUser();
- }, [fetchUser]);
+ if (hasVerifiedEmail || (!showEmail && !showPassword && !showLoading)) {
+ history.push(redirect || '/');
+ }
+ }, [showEmail, showPassword, showLoading, hasVerifiedEmail]);
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();
+ if (emailToVerify && emailOnlyLogin) {
+ doUserSignIn(emailToVerify);
}
- }, [hasVerifiedEmail, claimReward, hasClaimedEmailAward, hasFetchedReward, syncEnabled, hasSynced, balance]);
+ }, [emailToVerify, emailOnlyLogin, doUserSignIn]);
- // 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 && ,
- showEmailVerification && ,
- showUserVerification && setHasSkippedRewards(true)} />,
- showChannelCreation && ,
- showFollowIntro && (
- {
- 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 && (
- {
- 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 && (
-
-
-
- ),
- showSyncPassword && ,
- showLoadingSpinner && (
-
-
-
- ),
- ];
-
- 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 ;
+ return (
+
+ {(showEmail || showPassword) && (
+
+ {showEmail && }
+ {showPassword && setEmailOnlyLogin(true)} />}
+
+ )}
+ {!showEmail && !showPassword && showLoading && (
+
+
+
+ )}
+
+ );
}
export default withRouter(UserSignIn);
diff --git a/ui/component/userSignInPassword/index.js b/ui/component/userSignInPassword/index.js
new file mode 100644
index 000000000..e394d1e96
--- /dev/null
+++ b/ui/component/userSignInPassword/index.js
@@ -0,0 +1,22 @@
+import { connect } from 'react-redux';
+import {
+ selectUser,
+ selectUserIsPending,
+ selectEmailToVerify,
+ selectEmailNewErrorMessage,
+ doUserSignIn,
+ doClearEmailEntry,
+} from 'lbryinc';
+import UserSignIn from './view';
+
+const select = state => ({
+ user: selectUser(state),
+ userFetchPending: selectUserIsPending(state),
+ emailToVerify: selectEmailToVerify(state),
+ errorMessage: selectEmailNewErrorMessage(state),
+});
+
+export default connect(select, {
+ doUserSignIn,
+ doClearEmailEntry,
+})(UserSignIn);
diff --git a/ui/component/userSignInPassword/view.jsx b/ui/component/userSignInPassword/view.jsx
new file mode 100644
index 000000000..03d6d1e65
--- /dev/null
+++ b/ui/component/userSignInPassword/view.jsx
@@ -0,0 +1,67 @@
+// @flow
+import React, { useState } from 'react';
+import { FormField, Form } from 'component/common/form';
+import Button from 'component/button';
+import Card from 'component/common/card';
+import analytics from 'analytics';
+import Nag from 'component/common/nag';
+import UserPasswordReset from 'component/userPasswordReset';
+
+type Props = {
+ errorMessage: ?string,
+ emailToVerify: ?string,
+ doClearEmailEntry: () => void,
+ doUserSignIn: (string, ?string) => void,
+ onHandleEmailOnly: () => void,
+};
+
+export default function UserSignInPassword(props: Props) {
+ const { errorMessage, doUserSignIn, emailToVerify, onHandleEmailOnly } = props;
+ const [password, setPassword] = useState('');
+ const [forgotPassword, setForgotPassword] = React.useState(false);
+
+ function handleSubmit() {
+ if (emailToVerify) {
+ doUserSignIn(emailToVerify, password);
+ analytics.emailProvidedEvent();
+ }
+ }
+
+ function handleChangeToSignIn() {
+ onHandleEmailOnly();
+ }
+
+ return (
+
+ {forgotPassword ? (
+
+ ) : (
+
+ setPassword(e.target.value)}
+ helper={ setForgotPassword(true)} />}
+ />
+
+
+
+
+
+
+ }
+ nag={errorMessage && }
+ />
+ )}
+
+ );
+}
diff --git a/ui/component/userSignOutButton/index.js b/ui/component/userSignOutButton/index.js
index 4fa19d951..b4f98eda7 100644
--- a/ui/component/userSignOutButton/index.js
+++ b/ui/component/userSignOutButton/index.js
@@ -1,14 +1,12 @@
import { connect } from 'react-redux';
import { doSignOut } from 'redux/actions/app';
+import { doClearEmailEntry, doClearPasswordEntry } from 'lbryinc';
import UserSignOutButton from './view';
const select = state => ({});
-const perform = dispatch => ({
- signOut: () => dispatch(doSignOut()),
-});
-
-export default connect(
- select,
- perform
-)(UserSignOutButton);
+export default connect(select, {
+ doSignOut,
+ doClearEmailEntry,
+ doClearPasswordEntry,
+})(UserSignOutButton);
diff --git a/ui/component/userSignOutButton/view.jsx b/ui/component/userSignOutButton/view.jsx
index b624f1b33..ca0dbfc92 100644
--- a/ui/component/userSignOutButton/view.jsx
+++ b/ui/component/userSignOutButton/view.jsx
@@ -5,13 +5,25 @@ import Button from 'component/button';
type Props = {
button: string,
label?: string,
- signOut: () => void,
+ doSignOut: () => void,
+ doClearEmailEntry: () => void,
+ doClearPasswordEntry: () => void,
};
function UserSignOutButton(props: Props) {
- const { button = 'link', signOut, label } = props;
+ const { button = 'link', doSignOut, doClearEmailEntry, doClearPasswordEntry, label } = props;
- return ;
+ return (
+ {
+ doClearPasswordEntry();
+ doClearEmailEntry();
+ doSignOut();
+ }}
+ />
+ );
}
export default UserSignOutButton;
diff --git a/ui/component/userSignUp/index.js b/ui/component/userSignUp/index.js
new file mode 100644
index 000000000..deaeb731c
--- /dev/null
+++ b/ui/component/userSignUp/index.js
@@ -0,0 +1,52 @@
+import * as SETTINGS from 'constants/settings';
+import { connect } from 'react-redux';
+import {
+ selectEmailToVerify,
+ selectUser,
+ selectAccessToken,
+ makeSelectIsRewardClaimPending,
+ selectClaimedRewards,
+ rewards as REWARD_TYPES,
+ doClaimRewardType,
+ doUserFetch,
+ selectUserIsPending,
+ selectYoutubeChannels,
+ selectGetSyncIsPending,
+ selectGetSyncErrorMessage,
+ selectSyncHash,
+} from 'lbryinc';
+import { selectMyChannelClaims, selectBalance, selectFetchingMyChannels, selectCreatingChannel } from 'lbry-redux';
+import { makeSelectClientSetting } from 'redux/selectors/settings';
+import UserSignIn from './view';
+
+const select = state => ({
+ emailToVerify: selectEmailToVerify(state),
+ user: selectUser(state),
+ accessToken: selectAccessToken(state),
+ channels: selectMyChannelClaims(state),
+ claimedRewards: selectClaimedRewards(state),
+ claimingReward: makeSelectIsRewardClaimPending()(state, {
+ reward_type: REWARD_TYPES.TYPE_CONFIRM_EMAIL,
+ }),
+ balance: selectBalance(state),
+ fetchingChannels: selectFetchingMyChannels(state),
+ youtubeChannels: selectYoutubeChannels(state),
+ userFetchPending: selectUserIsPending(state),
+ syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
+ syncingWallet: selectGetSyncIsPending(state),
+ getSyncError: selectGetSyncErrorMessage(state),
+ hasSynced: Boolean(selectSyncHash(state)),
+ creatingChannel: selectCreatingChannel(state),
+});
+
+const perform = dispatch => ({
+ fetchUser: () => dispatch(doUserFetch()),
+ claimReward: () =>
+ dispatch(
+ doClaimRewardType(REWARD_TYPES.TYPE_CONFIRM_EMAIL, {
+ notifyError: false,
+ })
+ ),
+});
+
+export default connect(select, perform)(UserSignIn);
diff --git a/ui/component/userSignUp/view.jsx b/ui/component/userSignUp/view.jsx
new file mode 100644
index 000000000..3b20c3dd5
--- /dev/null
+++ b/ui/component/userSignUp/view.jsx
@@ -0,0 +1,215 @@
+// @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,
+ balance: ?number,
+ fetchingChannels: boolean,
+ claimingReward: boolean,
+ claimReward: () => void,
+ fetchUser: () => void,
+ claimedRewards: Array,
+ history: { replace: string => void },
+ location: { search: string },
+ youtubeChannels: Array,
+ 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;
+ 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;
+ const showEmailVerification = emailToVerify && !hasVerifiedEmail;
+ 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 && ,
+ showEmailVerification && ,
+ showUserVerification && setHasSkippedRewards(true)} />,
+ showChannelCreation && ,
+ showFollowIntro && (
+ {
+ 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 && (
+ {
+ 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 && (
+
+
+
+ ),
+ showSyncPassword && ,
+ showLoadingSpinner && (
+
+
+
+ ),
+ ];
+
+ 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 ;
+}
+
+export default withRouter(UserSignIn);
diff --git a/ui/component/userTagFollowIntro/view.jsx b/ui/component/userTagFollowIntro/view.jsx
index 9a7ad8d9e..9d5f2318f 100644
--- a/ui/component/userTagFollowIntro/view.jsx
+++ b/ui/component/userTagFollowIntro/view.jsx
@@ -4,6 +4,7 @@ import Nag from 'component/common/nag';
import TagsSelect from 'component/tagsSelect';
import Button from 'component/button';
import { Form } from 'component/common/form';
+import Card from 'component/common/card';
type Props = {
subscribedChannels: Array,
@@ -16,36 +17,40 @@ function UserChannelFollowIntro(props: Props) {
const followingCount = (followedTags && followedTags.length) || 0;
return (
-
- {__('Tag Selection')}
- {__('Select some tags to help us show you interesting things.')}
-
-
-
-
-
-
-
- {followingCount > 0 && (
-
- )}
-
-
+
+
+
+
+
+
+
+
+ {followingCount > 0 && (
+
+ )}
+
+
+ }
+ />
);
}
diff --git a/ui/component/userVerify/view.jsx b/ui/component/userVerify/view.jsx
index 7d91b733e..83149ff50 100644
--- a/ui/component/userVerify/view.jsx
+++ b/ui/component/userVerify/view.jsx
@@ -92,7 +92,7 @@ class UserVerify extends React.PureComponent {
)}
actions={
- {errorMessage && {errorMessage}
}
+ {errorMessage && {errorMessage}
}
{
}
/>
{!!Object.keys(errors).length || (
-
+
{(!!values.address && touched.address && errors.address) ||
(!!values.amount && touched.amount && errors.amount) ||
(parseFloat(values.amount) === balance &&
diff --git a/ui/constants/icons.js b/ui/constants/icons.js
index 1f4cb3c6d..fb30439e0 100644
--- a/ui/constants/icons.js
+++ b/ui/constants/icons.js
@@ -84,6 +84,7 @@ export const EYE = 'Eye';
export const EYE_OFF = 'EyeOff';
export const SIGN_OUT = 'SignOut';
export const SIGN_IN = 'SignIn';
+export const SIGN_UP = 'Key';
export const TRENDING = 'Trending';
export const TOP = 'Top';
export const NEW = 'New';
diff --git a/ui/constants/pages.js b/ui/constants/pages.js
index 00bbcf6fa..dd71323cc 100644
--- a/ui/constants/pages.js
+++ b/ui/constants/pages.js
@@ -1,5 +1,7 @@
-exports.AUTH = 'signin';
+exports.AUTH = 'signup';
+exports.AUTH_SIGNIN = 'signin';
exports.AUTH_VERIFY = 'verify';
+exports.AUTH_PASSWORD_SET = 'reset';
exports.BACKUP = 'backup';
exports.CHANNEL = 'channel';
exports.DISCOVER = 'discover';
diff --git a/ui/modal/modalRemoveFile/view.jsx b/ui/modal/modalRemoveFile/view.jsx
index 2b5e70db5..971f173fb 100644
--- a/ui/modal/modalRemoveFile/view.jsx
+++ b/ui/modal/modalRemoveFile/view.jsx
@@ -55,7 +55,7 @@ function ModalRemoveFile(props: Props) {
onChange={() => setAbandonChecked(!abandonChecked)}
/>
{abandonChecked === true && (
- This action is permanent and cannot be undone.
+ This action is permanent and cannot be undone.
)}
{/* @if TARGET='app' */}
diff --git a/ui/modal/modalRevokeClaim/view.jsx b/ui/modal/modalRevokeClaim/view.jsx
index 15aeab54f..c3243b4d9 100644
--- a/ui/modal/modalRevokeClaim/view.jsx
+++ b/ui/modal/modalRevokeClaim/view.jsx
@@ -71,7 +71,7 @@ export default function ModalRevokeClaim(props: Props) {
'This will prevent others from resolving and accessing the content you published. It will return the LBC to your spendable balance, less a small transaction fee.'
)}
- {__('FINAL WARNING: This action is permanent and cannot be undone.')}
+ {__('FINAL WARNING: This action is permanent and cannot be undone.')}
);
}
diff --git a/ui/modal/modalWalletEncrypt/view.jsx b/ui/modal/modalWalletEncrypt/view.jsx
index 53e0fbf1b..39ed8f986 100644
--- a/ui/modal/modalWalletEncrypt/view.jsx
+++ b/ui/modal/modalWalletEncrypt/view.jsx
@@ -168,7 +168,7 @@ class ModalWalletEncrypt extends React.PureComponent {
name="wallet-understand"
onChange={event => this.onChangeUnderstandConfirm(event)}
/>
- {failMessage && {__(failMessage)}
}
+ {failMessage && {__(failMessage)}
}
diff --git a/ui/modal/walletReceive/view.jsx b/ui/modal/walletReceive/view.jsx
deleted file mode 100644
index 85746bff8..000000000
--- a/ui/modal/walletReceive/view.jsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-import WalletAddress from 'component/walletAddress';
-import Page from 'component/page';
-
-const WalletAddressPage = () => (
-
-
-
-);
-
-export default WalletAddressPage;
diff --git a/ui/modal/walletSend/view.jsx b/ui/modal/walletSend/view.jsx
deleted file mode 100644
index 57bc0dacc..000000000
--- a/ui/modal/walletSend/view.jsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from 'react';
-import WalletSend from 'component/walletSend';
-
-const WalletSendModal = () => (
-
-
-
-);
-
-export default WalletSendModal;
diff --git a/ui/page/passwordSet/index.js b/ui/page/passwordSet/index.js
new file mode 100644
index 000000000..e3cf8568c
--- /dev/null
+++ b/ui/page/passwordSet/index.js
@@ -0,0 +1,3 @@
+import PasswordResetPage from './view';
+
+export default PasswordResetPage;
diff --git a/ui/page/passwordSet/view.jsx b/ui/page/passwordSet/view.jsx
new file mode 100644
index 000000000..671b60aa7
--- /dev/null
+++ b/ui/page/passwordSet/view.jsx
@@ -0,0 +1,12 @@
+// @flow
+import React from 'react';
+import UserPasswordSet from 'component/userPasswordSet';
+import Page from 'component/page';
+
+export default function PasswordResetPage() {
+ return (
+
+
+
+ );
+}
diff --git a/ui/page/rewards/view.jsx b/ui/page/rewards/view.jsx
index f1c8a4cd3..cd373b847 100644
--- a/ui/page/rewards/view.jsx
+++ b/ui/page/rewards/view.jsx
@@ -94,7 +94,7 @@ class RewardsPage extends PureComponent
{
return (
{__('Rewards Disabled')}
-
+
}}>
Rewards are currently disabled for your account. Turn on diagnostic data sharing, in %settings%, to
re-enable them.
diff --git a/ui/page/settings/view.jsx b/ui/page/settings/view.jsx
index 31d9143c7..8d06b4d39 100644
--- a/ui/page/settings/view.jsx
+++ b/ui/page/settings/view.jsx
@@ -18,6 +18,7 @@ import { SETTINGS } from 'lbry-redux';
import Card from 'component/common/card';
import { getPasswordFromCookie } from 'util/saved-passwords';
import Spinner from 'component/spinner';
+import SettingAccountPassword from 'component/settingAccountPassword';
// @if TARGET='app'
export const IS_MAC = process.platform === 'darwin';
@@ -261,11 +262,12 @@ class SettingsPage extends React.PureComponent {
{!IS_WEB && noDaemonSettings ? (
- {__('Failed to load settings.')}
+ {__('Failed to load settings.')}
) : (
} />
+ {isAuthenticated &&
}
{/* @if TARGET='app' */}
({});
-const perform = () => ({});
-
-export default connect(
- select,
- perform
-)(SignUpPage);
+export default SignInPage;
diff --git a/ui/page/signInVerify/view.jsx b/ui/page/signInVerify/view.jsx
index 203467a4e..ef4b4883a 100644
--- a/ui/page/signInVerify/view.jsx
+++ b/ui/page/signInVerify/view.jsx
@@ -1,4 +1,5 @@
// @flow
+import * as PAGES from 'constants/pages';
import React, { useState } from 'react';
import { withRouter } from 'react-router';
import Page from 'component/page';
@@ -6,7 +7,7 @@ import ReCAPTCHA from 'react-google-recaptcha';
import Button from 'component/button';
import { Lbryio } from 'lbryinc';
import I18nMessage from 'component/i18nMessage';
-import * as PAGES from 'constants/pages';
+import Card from 'component/common/card';
type Props = {
history: { push: string => void, location: { search: string } },
@@ -88,40 +89,49 @@ function SignInVerifyPage(props: Props) {
return (
-
-
- {isAuthenticationSuccess ? __('Sign In Success!') : __('Sign In to lbry.tv')}
-
-
- {isAuthenticationSuccess
- ? __('You can now close this tab.')
- : needsRecaptcha
- ? __('Click below to sign in to lbry.tv')
- : __('Welcome back! You are automatically being signed in.')}
-
-
- {showCaptchaMessage && !isAuthenticationSuccess && (
- window.location.reload()} />,
- }}
- >
- Not seeing a captcha? Check your ad blocker or try %refresh%.
-
- )}
-
- {!isAuthenticationSuccess && needsRecaptcha && (
-
-
-
- )}
-
+
+
+
+ {isAuthenticationSuccess
+ ? __('You can now close this tab.')
+ : needsRecaptcha
+ ? __('Click below to sign in to lbry.tv')
+ : __('Welcome back! You are automatically being signed in.')}
+
+ {showCaptchaMessage && !isAuthenticationSuccess && (
+
+ window.location.reload()} />
+ ),
+ }}
+ >
+ Not seeing a captcha? Check your ad blocker or try %refresh%.
+
+
+ )}
+
+ }
+ actions={
+ !isAuthenticationSuccess &&
+ needsRecaptcha && (
+
+
+
+ )
+ }
+ />
+
);
}
diff --git a/ui/page/signUp/index.js b/ui/page/signUp/index.js
new file mode 100644
index 000000000..3528a6761
--- /dev/null
+++ b/ui/page/signUp/index.js
@@ -0,0 +1,3 @@
+import SignUpPage from './view';
+
+export default SignUpPage;
diff --git a/ui/page/signUp/view.jsx b/ui/page/signUp/view.jsx
new file mode 100644
index 000000000..a2e505a25
--- /dev/null
+++ b/ui/page/signUp/view.jsx
@@ -0,0 +1,12 @@
+// @flow
+import React from 'react';
+import UserSignUp from 'component/userSignUp';
+import Page from 'component/page';
+
+export default function SignUpPage() {
+ return (
+
+
+
+ );
+}
diff --git a/ui/scss/component/_button.scss b/ui/scss/component/_button.scss
index bf674ba7d..764aac5c1 100644
--- a/ui/scss/component/_button.scss
+++ b/ui/scss/component/_button.scss
@@ -46,6 +46,11 @@
}
}
+.button--header-close {
+ background-color: var(--color-primary-alt);
+ padding: var(--spacing-small);
+}
+
.button--download-link {
.button__label {
white-space: normal;
diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss
index 414087c2c..bdb708589 100644
--- a/ui/scss/component/_card.scss
+++ b/ui/scss/component/_card.scss
@@ -138,6 +138,13 @@
& > *:not(:last-child) {
margin-right: var(--spacing-medium);
}
+
+ /* .badge rule inherited from file page prices, should be refactored */
+ .badge {
+ float: right;
+ margin-left: var(--spacing-small);
+ margin-top: 8px; // should be flex'd, but don't blame me! I just moved it down 3px
+ }
}
.card__title.card__title--deprecated {
@@ -172,19 +179,6 @@
justify-content: space-between;
}
-.card__title {
- font-size: var(--font-title);
- font-weight: var(--font-weight-light);
- display: block;
-
- /* .badge rule inherited from file page prices, should be refactored */
- .badge {
- float: right;
- margin-left: var(--spacing-small);
- margin-top: 8px; // should be flex'd, but don't blame me! I just moved it down 3px
- }
-}
-
.card__subtitle {
color: var(--color-text-subtitle);
margin: var(--spacing-small) 0;
@@ -245,3 +239,19 @@
margin-bottom: var(--spacing-small);
}
}
+
+.card__bottom-gutter {
+ @extend .help;
+ display: flex;
+ align-items: center;
+ margin-top: var(--spacing-medium);
+
+ &:only-child,
+ &:first-child {
+ margin-top: 0;
+ }
+
+ > *:not(:last-child) {
+ margin-right: var(--spacing-medium);
+ }
+}
diff --git a/ui/scss/component/_claim-list.scss b/ui/scss/component/_claim-list.scss
index 9b5ea21b8..298374211 100644
--- a/ui/scss/component/_claim-list.scss
+++ b/ui/scss/component/_claim-list.scss
@@ -95,6 +95,11 @@
margin-right: var(--spacing-medium);
}
+ .channel-thumbnail {
+ width: 6rem;
+ height: 6rem;
+ }
+
&:hover {
.claim-preview__hover-actions {
display: block;
diff --git a/ui/scss/component/_header.scss b/ui/scss/component/_header.scss
index a7c499716..7b8398015 100644
--- a/ui/scss/component/_header.scss
+++ b/ui/scss/component/_header.scss
@@ -181,3 +181,13 @@
margin-top: 0;
margin-left: var(--spacing-small);
}
+
+.header__auth-buttons {
+ display: flex;
+ align-items: center;
+ font-weight: var(--font-weight-bold);
+
+ & > *:not(:last-child) {
+ margin: 0 var(--spacing-medium);
+ }
+}
diff --git a/ui/scss/component/_main.scss b/ui/scss/component/_main.scss
index eceb06304..5858fc1ab 100644
--- a/ui/scss/component/_main.scss
+++ b/ui/scss/component/_main.scss
@@ -34,7 +34,7 @@
}
.main--auth-page {
- max-width: 60rem;
+ max-width: 70rem;
margin-top: var(--spacing-main-padding);
margin-left: auto;
margin-right: auto;
@@ -61,11 +61,10 @@
.main--contained {
margin: auto;
- margin-top: 2rem;
display: flex;
flex-direction: column;
align-items: flex-start;
- max-width: 40rem;
+ max-width: 50rem;
text-align: left;
& > * {
@@ -76,3 +75,16 @@
.main--full-width {
width: 100%;
}
+
+.main__sign-in,
+.main__sign-up {
+ max-width: 27rem;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.main__channel-creation {
+ margin-left: auto;
+ margin-right: auto;
+ max-width: 32rem;
+}
diff --git a/ui/scss/component/_modal.scss b/ui/scss/component/_modal.scss
index 51295e915..a3f87a96f 100644
--- a/ui/scss/component/_modal.scss
+++ b/ui/scss/component/_modal.scss
@@ -1,7 +1,9 @@
.ReactModal__Body--open {
#app {
- height: 100vh;
- overflow-y: hidden;
+ @media (max-width: $breakpoint-small) {
+ height: 100vh;
+ overflow-y: hidden;
+ }
}
}
diff --git a/ui/scss/component/nag.scss b/ui/scss/component/nag.scss
index e14e2e7ab..e0a3a2cc9 100644
--- a/ui/scss/component/nag.scss
+++ b/ui/scss/component/nag.scss
@@ -32,6 +32,10 @@ $nag-error-z-index: 100001;
z-index: 1 !important; /* booooooo */
}
+.nag--relative {
+ position: relative;
+}
+
.nag--helpful {
background-color: var(--color-secondary);
color: var(--color-white);
diff --git a/ui/scss/component/section.scss b/ui/scss/component/section.scss
index 15a332ea8..ad5e6e6b3 100644
--- a/ui/scss/component/section.scss
+++ b/ui/scss/component/section.scss
@@ -1,9 +1,8 @@
.section {
position: relative;
- margin-top: var(--spacing-large);
- &:first-of-type {
- margin-top: 0;
+ ~ .section {
+ margin-top: var(--spacing-large);
}
}
@@ -51,6 +50,12 @@
}
}
+.section__subtitle {
+ color: var(--color-text-subtitle);
+ margin: var(--spacing-small) 0;
+ font-size: var(--font-body);
+}
+
.section__subtitle--status {
@extend .section__subtitle;
padding: var(--spacing-small);
@@ -82,6 +87,42 @@
margin-top: var(--spacing-medium);
}
+.section__actions {
+ display: flex;
+ align-items: center;
+ margin-top: var(--spacing-large);
+
+ &:only-child,
+ &:first-child {
+ margin-top: 0;
+ }
+
+ > *:not(:last-child) {
+ margin-right: var(--spacing-medium);
+ }
+
+ @media (max-width: $breakpoint-small) {
+ flex-wrap: wrap;
+
+ > * {
+ margin-bottom: var(--spacing-small);
+ }
+ }
+
+ .button--primary,
+ .button ~ .button--link {
+ &:focus {
+ @include focus;
+ }
+ }
+
+ .button--primary ~ .button--link {
+ font-weight: var(--font-weight-bold);
+ margin-left: var(--spacing-small);
+ padding: var(--spacing-xsmall);
+ }
+}
+
.section__actions--centered {
@extend .section__actions;
justify-content: center;
@@ -94,13 +135,3 @@
.section__actions--no-margin {
margin-top: 0;
}
-
-@media (max-width: $breakpoint-small) {
- .section__actions {
- flex-wrap: wrap;
-
- > * {
- margin-bottom: var(--spacing-small);
- }
- }
-}
diff --git a/ui/scss/init/_gui.scss b/ui/scss/init/_gui.scss
index 0b3a91316..24ed8228b 100644
--- a/ui/scss/init/_gui.scss
+++ b/ui/scss/init/_gui.scss
@@ -201,7 +201,7 @@ img {
display: block;
font-size: var(--font-small);
color: var(--color-text-help);
- margin-top: var(--spacing-miniscule);
+ margin-top: var(--spacing-small);
margin-bottom: var(--spacing-small);
}
@@ -238,13 +238,13 @@ img {
}
}
-.error-wrapper {
+.error__wrapper {
background-color: var(--color-error);
padding: var(--spacing-small);
border-radius: var(--border-radius);
}
-.error-text {
+.error__text {
color: var(--color-text-error);
}
diff --git a/ui/scss/themes/light.scss b/ui/scss/themes/light.scss
index f2d94f9e6..031f0fa10 100644
--- a/ui/scss/themes/light.scss
+++ b/ui/scss/themes/light.scss
@@ -6,7 +6,7 @@
--color-header-button: #f7f7f7;
// Color
- --color-background: #f7f7f7;
+ --color-background: #f9f9f9;
--color-background--splash: #212529;
--color-border: #ededed;
--color-background-overlay: #21252980;
diff --git a/ui/util/saved-passwords.js b/ui/util/saved-passwords.js
index 0b3b70861..6dbde307e 100644
--- a/ui/util/saved-passwords.js
+++ b/ui/util/saved-passwords.js
@@ -2,7 +2,8 @@ const { DOMAIN } = require('../../config.js');
const AUTH_TOKEN = 'auth_token';
const SAVED_PASSWORD = 'saved_password';
const DEPRECATED_SAVED_PASSWORD = 'saved-password';
-const domain = typeof window === 'object' ? window.location.hostname : DOMAIN;
+const domain =
+ typeof window === 'object' && window.location.hostname.includes('localhost') ? window.location.hostname : DOMAIN;
const isProduction = process.env.NODE_ENV === 'production';
const maxExpiration = 2147483647;
let sessionPassword;
diff --git a/yarn.lock b/yarn.lock
index 3df2c0411..2aa2dee46 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -819,10 +819,10 @@
prop-types "^15.6.2"
scheduler "^0.18.0"
-"@lbry/components@^4.0.1":
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/@lbry/components/-/components-4.0.1.tgz#8dcf7348920383d854c0db640faaf1ac5a72f7ef"
- integrity sha512-vY84ziZ9EaXoezDBK2VsajvXcSPXDV0fr1VWn2w0iHkGa756RWvNySpnqaKMZH+myK12mvNNc/NkGIW5oO7+5w==
+"@lbry/components@^4.1.2":
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/@lbry/components/-/components-4.1.2.tgz#18eda2f1a73a6241e9a96594ccab8fffb9ef3ae9"
+ integrity sha512-GJx4BTdEtOlm5/JsKUVXBzYeepXTbZ4GjGFrKxzXh6jWw40aFOh8OrHwYM/LHRl1fuKRmB5xpZNWsjw6EKyEeQ==
"@mapbox/hast-util-table-cell-style@^0.1.3":
version "0.1.3"
@@ -6147,9 +6147,9 @@ lbry-redux@lbryio/lbry-redux#1097a63d44a20b87e443fbaa48f95fe3ea5e3f70:
reselect "^3.0.0"
uuid "^3.3.2"
-lbryinc@lbryio/lbryinc#0addc624db54000b0447f4539f91f5758d26eef3:
+lbryinc@lbryio/lbryinc#12aefaa14343d2f3eac01f2683701f58e53f1848:
version "0.0.1"
- resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/0addc624db54000b0447f4539f91f5758d26eef3"
+ resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/12aefaa14343d2f3eac01f2683701f58e53f1848"
dependencies:
reselect "^3.0.0"