add sign in flow to lbry.tv
This commit is contained in:
parent
3c08913d1c
commit
a1f0e485b5
7 changed files with 127 additions and 17 deletions
|
@ -150,6 +150,7 @@
|
|||
"react-dom": "^16.8.2",
|
||||
"react-draggable": "^3.3.0",
|
||||
"react-ga": "^2.5.7",
|
||||
"react-google-recaptcha": "^2.0.1",
|
||||
"react-hot-loader": "^4.11.1",
|
||||
"react-modal": "^3.1.7",
|
||||
"react-paginate": "^5.2.1",
|
||||
|
|
|
@ -44,9 +44,14 @@ const Header = (props: Props) => {
|
|||
} = props;
|
||||
const authenticated = Boolean(email);
|
||||
|
||||
//on the verify page don't let anyone escape other than by closing the tab to keep session data consistent
|
||||
const isVerifyPage = history.location.pathname.includes(PAGES.AUTH_VERIFY);
|
||||
|
||||
// Sign out if they click the "x" when they are on the password prompt
|
||||
const authHeaderAction = syncError ? { onClick: signOut } : { navigate: '/' };
|
||||
const homeButtonNavigationProps = authHeader ? authHeaderAction : { navigate: '/' };
|
||||
const homeButtonNavigationProps = isVerifyPage ?
|
||||
{ } :
|
||||
authHeader ? authHeaderAction : { navigate: '/' };
|
||||
const closeButtonNavigationProps = authHeader ? authHeaderAction : { onClick: () => history.goBack() };
|
||||
|
||||
function handleThemeToggle() {
|
||||
|
@ -111,7 +116,7 @@ const Header = (props: Props) => {
|
|||
)}
|
||||
{/* @endif */}
|
||||
|
||||
{!authHeader && <WunderBar />}
|
||||
{!authHeader && <WunderBar/>}
|
||||
</div>
|
||||
|
||||
{!authHeader ? (
|
||||
|
@ -122,37 +127,37 @@ const Header = (props: Props) => {
|
|||
<MenuButton className="header__navigation-item menu__title">{getWalletTitle()}</MenuButton>
|
||||
<MenuList className="menu__list--header">
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.WALLET}`)}>
|
||||
<Icon aria-hidden icon={ICONS.WALLET} />
|
||||
<Icon aria-hidden icon={ICONS.WALLET}/>
|
||||
{__('Wallet')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.REWARDS}`)}>
|
||||
<Icon aria-hidden icon={ICONS.FEATURED} />
|
||||
<Icon aria-hidden icon={ICONS.FEATURED}/>
|
||||
{__('Rewards')}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
<Menu>
|
||||
<MenuButton className="header__navigation-item menu__title">
|
||||
<Icon size={18} icon={ICONS.ACCOUNT} />
|
||||
<Icon size={18} icon={ICONS.ACCOUNT}/>
|
||||
</MenuButton>
|
||||
<MenuList className="menu__list--header">
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.ACCOUNT}`)}>
|
||||
<Icon aria-hidden icon={ICONS.OVERVIEW} />
|
||||
<Icon aria-hidden icon={ICONS.OVERVIEW}/>
|
||||
{__('Overview')}
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.PUBLISH}`)}>
|
||||
<Icon aria-hidden icon={ICONS.PUBLISH} />
|
||||
<Icon aria-hidden icon={ICONS.PUBLISH}/>
|
||||
{__('Publish')}
|
||||
</MenuItem>
|
||||
{authenticated ? (
|
||||
<MenuItem className="menu__link" onSelect={signOut}>
|
||||
<Icon aria-hidden icon={ICONS.SIGN_OUT} />
|
||||
<Icon aria-hidden icon={ICONS.SIGN_OUT}/>
|
||||
{__('Sign Out')}
|
||||
</MenuItem>
|
||||
) : (
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.AUTH}`)}>
|
||||
<Icon aria-hidden icon={ICONS.SIGN_IN} />
|
||||
<Icon aria-hidden icon={ICONS.SIGN_IN}/>
|
||||
{__('Sign In')}
|
||||
</MenuItem>
|
||||
)}
|
||||
|
@ -161,33 +166,33 @@ const Header = (props: Props) => {
|
|||
|
||||
<Menu>
|
||||
<MenuButton className="header__navigation-item menu__title">
|
||||
<Icon size={18} icon={ICONS.SETTINGS} />
|
||||
<Icon size={18} icon={ICONS.SETTINGS}/>
|
||||
</MenuButton>
|
||||
<MenuList className="menu__list--header">
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.SETTINGS}`)}>
|
||||
<Icon aria-hidden tootlip icon={ICONS.SETTINGS} />
|
||||
<Icon aria-hidden tootlip icon={ICONS.SETTINGS}/>
|
||||
{__('Settings')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.HELP}`)}>
|
||||
<Icon aria-hidden icon={ICONS.HELP} />
|
||||
<Icon aria-hidden icon={ICONS.HELP}/>
|
||||
{__('Help')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={handleThemeToggle}>
|
||||
<Icon icon={currentTheme === 'light' ? ICONS.DARK : ICONS.LIGHT} />
|
||||
<Icon icon={currentTheme === 'light' ? ICONS.DARK : ICONS.LIGHT}/>
|
||||
{currentTheme === 'light' ? __('Dark') : __('Light')}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Button navigate={`/$/${PAGES.AUTH}`} button="primary" label={__('Sign In')} />
|
||||
<Button navigate={`/$/${PAGES.AUTH}`} button="primary" label={__('Sign In')}/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
) : (!isVerifyPage &&
|
||||
<div className="header__menu">
|
||||
{/* Add an empty span here so we can use the same style as above */}
|
||||
{/* This pushes the close button to the right side */}
|
||||
<span />
|
||||
<span/>
|
||||
<Tooltip label={__('Go Back')}>
|
||||
<Button icon={ICONS.REMOVE} {...closeButtonNavigationProps} />
|
||||
</Tooltip>
|
||||
|
|
|
@ -24,6 +24,7 @@ import FollowingPage from 'page/following';
|
|||
import ListBlockedPage from 'page/listBlocked';
|
||||
import FourOhFourPage from 'page/fourOhFour';
|
||||
import SignInPage from 'page/signIn';
|
||||
import SignInVerifyPage from 'page/signInVerify';
|
||||
import ChannelsPage from 'page/channels';
|
||||
|
||||
// Tell the browser we are handling scroll restoration
|
||||
|
@ -77,6 +78,7 @@ function AppRouter(props: Props) {
|
|||
<Route path={`/$/${PAGES.AUTH}`} exact component={SignInPage} />
|
||||
<Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} />
|
||||
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
|
||||
<Route path={`/$/${PAGES.AUTH_VERIFY}`} exact component={SignInVerifyPage} />
|
||||
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
|
||||
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.INVITE}`} component={InvitePage} />
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export const AUTH = 'signin';
|
||||
export const AUTH_VERIFY = 'verify';
|
||||
export const BACKUP = 'backup';
|
||||
export const CHANNEL = 'channel';
|
||||
export const DISCOVER = 'discover';
|
||||
|
|
13
ui/page/signInVerify/index.js
Normal file
13
ui/page/signInVerify/index.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doToast } from 'lbry-redux';
|
||||
import SignInVerifyPage from './view';
|
||||
|
||||
const select = () => ({});
|
||||
const perform = {
|
||||
doToast,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(SignInVerifyPage);
|
72
ui/page/signInVerify/view.jsx
Normal file
72
ui/page/signInVerify/view.jsx
Normal file
|
@ -0,0 +1,72 @@
|
|||
// @flow
|
||||
import React, { useState } from 'react';
|
||||
import { withRouter } from 'react-router';
|
||||
import Page from 'component/page';
|
||||
import ReCAPTCHA from "react-google-recaptcha";
|
||||
import Button from 'component/button';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import * as PAGES from 'constants/pages';
|
||||
|
||||
type Props = {
|
||||
history: { push: string => void },
|
||||
doToast: ({}) => void
|
||||
};
|
||||
|
||||
function SignInVerifyPage(props: Props) {
|
||||
const { history: { push }, doToast } = props;
|
||||
const urlParams = new URLSearchParams(location.search);
|
||||
const authToken = urlParams.get('auth_token');
|
||||
const userSubmittedEmail = urlParams.get('email');
|
||||
const verificationToken = urlParams.get('verification_token');
|
||||
|
||||
const [isAuthenticationSuccess, setIsAuthenticationSuccess] = useState(false);
|
||||
|
||||
function onAuthError(message) {
|
||||
doToast({
|
||||
message: message || __('Authentication failure.'),
|
||||
isError: true,
|
||||
});
|
||||
//push(`/$/${PAGES.AUTH}`);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!authToken || !userSubmittedEmail || !verificationToken) {
|
||||
onAuthError(__('Invalid or expired sign-in link.'));
|
||||
}
|
||||
}, [authToken, userSubmittedEmail, verificationToken, doToast, push]);
|
||||
|
||||
function onCaptchaChange(value) {
|
||||
Lbryio.call('user_email', 'confirm', {
|
||||
auth_token: authToken,
|
||||
email: userSubmittedEmail,
|
||||
verification_token: verificationToken,
|
||||
recaptcha: value,
|
||||
})
|
||||
.then(() => {
|
||||
setIsAuthenticationSuccess(true);
|
||||
})
|
||||
.catch(() => {
|
||||
onAuthError(__('Invalid captcha response or other authentication error.'));
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Page authPage className="main--auth-page">
|
||||
<section className="main--contained">
|
||||
<h1 className="section__title--large">{isAuthenticationSuccess ? __('Sign In Success!') : __('Sign In to lbry.tv') }</h1>
|
||||
<p className="section__subtitle">{ isAuthenticationSuccess ? __('You can now close this tab.') : __('Click below to sign in to lbry.tv') }</p>
|
||||
{ !isAuthenticationSuccess &&
|
||||
<div className="section__actions">
|
||||
<ReCAPTCHA
|
||||
sitekey="6LePsJgUAAAAAFTuWOKRLnyoNKhm0HA4C3elrFMG"
|
||||
onChange={onCaptchaChange}
|
||||
onExpired={onAuthError}
|
||||
onErrored={onAuthError}
|
||||
/>
|
||||
</div>}
|
||||
</section>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default withRouter(SignInVerifyPage);
|
18
yarn.lock
18
yarn.lock
|
@ -9635,7 +9635,7 @@ promise@^7.1.1:
|
|||
dependencies:
|
||||
asap "~2.0.3"
|
||||
|
||||
prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
version "15.7.2"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
||||
|
@ -9870,6 +9870,14 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.1, rc@^1.2.7, rc@^1.2.8:
|
|||
minimist "^1.2.0"
|
||||
strip-json-comments "~2.0.1"
|
||||
|
||||
react-async-script@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-async-script/-/react-async-script-1.1.1.tgz#f481c6c5f094bf4b94a9d52da0d0dda2e1a74bdf"
|
||||
integrity sha512-pmgS3O7JcX4YtH/Xy//NXylpD5CNb5T4/zqlVUV3HvcuyOanatvuveYoxl3X30ZSq/+q/+mSXcNS8xDVQJpSeA==
|
||||
dependencies:
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
prop-types "^15.5.0"
|
||||
|
||||
react-compound-slider@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/react-compound-slider/-/react-compound-slider-1.2.2.tgz#1cc6809bb2d0282ad90a06040e2b10635edfbea3"
|
||||
|
@ -9910,6 +9918,14 @@ react-ga@^2.5.7:
|
|||
resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-2.5.7.tgz#1c80a289004bf84f84c26d46f3a6a6513081bf2e"
|
||||
integrity sha512-UmATFaZpEQDO96KFjB5FRLcT6hFcwaxOmAJZnjrSiFN/msTqylq9G+z5Z8TYzN/dbamDTiWf92m6MnXXJkAivQ==
|
||||
|
||||
react-google-recaptcha@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-google-recaptcha/-/react-google-recaptcha-2.0.1.tgz#3276b29659493f7ca2a5b7739f6c239293cdf1d8"
|
||||
integrity sha512-4Y8awVnarn7+gdVpu8uvSmRJzzlMMoXqdhLoyToTOfVK6oM+NaChNI8NShnu75Q2YGHLvR1IA1FWZesuYHwn5w==
|
||||
dependencies:
|
||||
prop-types "^15.5.0"
|
||||
react-async-script "^1.1.1"
|
||||
|
||||
react-hot-loader@^4.11.1:
|
||||
version "4.11.1"
|
||||
resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.11.1.tgz#2cabbd0f1c8a44c28837b86d6ce28521e6d9a8ac"
|
||||
|
|
Loading…
Reference in a new issue