header changes with sign in/sign out

This commit is contained in:
Sean Yesmunt 2019-08-21 16:54:44 -04:00
parent b0a34cfc46
commit 3311e1af1f
20 changed files with 204 additions and 170 deletions

View file

@ -20,4 +20,7 @@ export const remote = {
require: callable, require: callable,
}; };
export const clipboard = {};
export const ipcRenderer = {};
export const isDev = false; export const isDev = false;

View file

@ -295,4 +295,11 @@ export const icons = {
<line x1="15" y1="12" x2="3" y2="12" /> <line x1="15" y1="12" x2="3" y2="12" />
</g> </g>
), ),
[ICONS.SIGN_OUT]: buildIcon(
<g>
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
<polyline points="16 17 21 12 16 7" />
<line x1="21" y1="12" x2="9" y2="12" />
</g>
),
}; };

View file

@ -1,10 +1,10 @@
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectBalance, SETTINGS as LBRY_REDUX_SETTINGS } from 'lbry-redux'; import { selectBalance, SETTINGS as LBRY_REDUX_SETTINGS } from 'lbry-redux';
import { selectUserEmail } from 'lbryinc';
import { formatCredits } from 'util/format-credits'; import { formatCredits } from 'util/format-credits';
import { doSetClientSetting } from 'redux/actions/settings'; import { doSetClientSetting } from 'redux/actions/settings';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
import Header from './view'; import Header from './view';
const select = state => ({ const select = state => ({
@ -14,6 +14,7 @@ const select = state => ({
currentTheme: makeSelectClientSetting(SETTINGS.THEME)(state), currentTheme: makeSelectClientSetting(SETTINGS.THEME)(state),
automaticDarkModeEnabled: makeSelectClientSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED)(state), automaticDarkModeEnabled: makeSelectClientSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED)(state),
hideBalance: makeSelectClientSetting(SETTINGS.HIDE_BALANCE)(state), hideBalance: makeSelectClientSetting(SETTINGS.HIDE_BALANCE)(state),
email: selectUserEmail(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({

View file

@ -1,7 +1,8 @@
// @flow // @flow
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import * as React from 'react'; import * as PAGES from 'constants/pages';
import React, { Fragment } from 'react';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import Button from 'component/button'; import Button from 'component/button';
import LbcSymbol from 'component/common/lbc-symbol'; import LbcSymbol from 'component/common/lbc-symbol';
@ -9,6 +10,22 @@ import WunderBar from 'component/wunderbar';
import Icon from 'component/common/icon'; import Icon from 'component/common/icon';
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button'; import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
// Move this into jessops password util
import cookie from 'cookie';
// @if TARGET='app'
import keytar from 'keytar';
// @endif;
function deleteAuthToken() {
// @if TARGET='app'
keytar.deletePassword('LBRY', 'auth_token').catch(console.error); //eslint-disable-line
// @endif;
// @if TARGET='web'
document.cookie = cookie.serialize('auth_token', '', {
expires: new Date(),
});
// @endif
}
type Props = { type Props = {
autoUpdateDownloaded: boolean, autoUpdateDownloaded: boolean,
balance: string, balance: string,
@ -20,10 +37,20 @@ type Props = {
automaticDarkModeEnabled: boolean, automaticDarkModeEnabled: boolean,
setClientSetting: (string, boolean | string) => void, setClientSetting: (string, boolean | string) => void,
hideBalance: boolean, hideBalance: boolean,
email: ?string,
}; };
const Header = (props: Props) => { const Header = (props: Props) => {
const { roundedBalance, history, setClientSetting, currentTheme, automaticDarkModeEnabled, hideBalance } = props; const {
roundedBalance,
history,
setClientSetting,
currentTheme,
automaticDarkModeEnabled,
hideBalance,
email,
} = props;
const isAuthenticated = Boolean(email);
function handleThemeToggle() { function handleThemeToggle() {
if (automaticDarkModeEnabled) { if (automaticDarkModeEnabled) {
@ -49,6 +76,12 @@ const Header = (props: Props) => {
return __('Account'); return __('Account');
} }
function signOut() {
// Replace this with actual clearUser function
window.store.dispatch({ type: 'USER_FETCH_FAILURE' });
deleteAuthToken();
}
return ( return (
<header className="header"> <header className="header">
<div className="header__contents"> <div className="header__contents">
@ -78,55 +111,67 @@ const Header = (props: Props) => {
/> />
</div> </div>
{/* @endif */} {/* @endif */}
<WunderBar />
</div> </div>
<WunderBar /> <div className="header__menu">
{isAuthenticated ? (
<Fragment>
<Menu>
<MenuButton className="header__navigation-item menu__title">
<Icon icon={ICONS.ACCOUNT} />
{getAccountTitle()}
</MenuButton>
<MenuList className="menu__list--header">
<MenuItem className="menu__link" onSelect={() => history.push(`/$/account`)}>
<Icon aria-hidden icon={ICONS.OVERVIEW} />
{__('Overview')}
</MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/rewards`)}>
<Icon aria-hidden icon={ICONS.FEATURED} />
{__('Rewards')}
</MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/wallet`)}>
<Icon aria-hidden icon={ICONS.WALLET} />
{__('Wallet')}
</MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/publish`)}>
<Icon aria-hidden icon={ICONS.PUBLISH} />
{__('Publish')}
</MenuItem>
<MenuItem className="menu__link" onSelect={signOut}>
<Icon aria-hidden icon={ICONS.SIGN_OUT} />
{__('Sign Out')}
</MenuItem>
</MenuList>
</Menu>
<div className="header__navigation"> <Menu>
<Menu> <MenuButton className="header__navigation-item menu__title">
<MenuButton className="header__navigation-item menu__title"> <Icon size={18} icon={ICONS.SETTINGS} />
<Icon icon={ICONS.ACCOUNT} /> </MenuButton>
{getAccountTitle()} <MenuList className="menu__list--header">
</MenuButton> <MenuItem className="menu__link" onSelect={() => history.push(`/$/settings`)}>
<MenuList> <Icon aria-hidden tootlip icon={ICONS.SETTINGS} />
<MenuItem className="menu__link" onSelect={() => history.push(`/$/account`)}> {__('Settings')}
<Icon aria-hidden icon={ICONS.OVERVIEW} /> </MenuItem>
{__('Overview')} <MenuItem className="menu__link" onSelect={() => history.push(`/$/help`)}>
</MenuItem> <Icon aria-hidden icon={ICONS.HELP} />
<MenuItem className="menu__link" onSelect={() => history.push(`/$/rewards`)}> {__('Help')}
<Icon aria-hidden icon={ICONS.FEATURED} /> </MenuItem>
{__('Rewards')} <MenuItem className="menu__link" onSelect={handleThemeToggle}>
</MenuItem> <Icon icon={currentTheme === 'light' ? ICONS.DARK : ICONS.LIGHT} />
<MenuItem className="menu__link" onSelect={() => history.push(`/$/wallet`)}> {currentTheme === 'light' ? 'Dark' : 'Light'}
<Icon aria-hidden icon={ICONS.WALLET} /> </MenuItem>
{__('Wallet')} </MenuList>
</MenuItem> </Menu>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/publish`)}> </Fragment>
<Icon aria-hidden icon={ICONS.PUBLISH} /> ) : (
{__('Publish')} <Fragment>
</MenuItem> <Button navigate={`/$/${PAGES.AUTH}/signin`} button="link" label={__('Sign In')} />
</MenuList> <Button navigate={`/$/${PAGES.AUTH}/signup`} button="primary" label={__('Sign Up')} />
</Menu> </Fragment>
)}
<Menu>
<MenuButton className="header__navigation-item menu__title">
<Icon size={18} icon={ICONS.SETTINGS} />
</MenuButton>
<MenuList>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/settings`)}>
<Icon aria-hidden tootlip icon={ICONS.SETTINGS} />
{__('Settings')}
</MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/help`)}>
<Icon aria-hidden icon={ICONS.HELP} />
{__('Help')}
</MenuItem>
<MenuItem className="menu__link" onSelect={handleThemeToggle}>
<Icon icon={currentTheme === 'light' ? ICONS.DARK : ICONS.LIGHT} />
{currentTheme === 'light' ? 'Dark' : 'Light'}
</MenuItem>
</MenuList>
</Menu>
</div> </div>
</div> </div>
</header> </header>

View file

@ -24,6 +24,8 @@ import FollowingPage from 'page/following';
import ListBlocked from 'page/listBlocked'; import ListBlocked from 'page/listBlocked';
import FourOhFourPage from 'page/fourOhFour'; import FourOhFourPage from 'page/fourOhFour';
import UserEmail from 'component/userEmail';
// Tell the browser we are handling scroll restoration // Tell the browser we are handling scroll restoration
if ('scrollRestoration' in history) { if ('scrollRestoration' in history) {
history.scrollRestoration = 'manual'; history.scrollRestoration = 'manual';
@ -35,7 +37,7 @@ type Props = {
}; };
function SignInPage() { function SignInPage() {
return <h1>Sign In</h1>; return <UserEmail />;
} }
function SignUpPage() { function SignUpPage() {

View file

@ -2,6 +2,7 @@ import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectSubscriptions } from 'redux/selectors/subscriptions'; import { selectSubscriptions } from 'redux/selectors/subscriptions';
import { selectFollowedTags } from 'lbry-redux'; import { selectFollowedTags } from 'lbry-redux';
import { selectUserEmail } from 'lbryinc';
import SideBar from './view'; import SideBar from './view';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
@ -9,6 +10,7 @@ const select = state => ({
subscriptions: selectSubscriptions(state), subscriptions: selectSubscriptions(state),
followedTags: selectFollowedTags(state), followedTags: selectFollowedTags(state),
language: makeSelectClientSetting(SETTINGS.LANGUAGE)(state), // trigger redraw on language change language: makeSelectClientSetting(SETTINGS.LANGUAGE)(state), // trigger redraw on language change
email: selectUserEmail(state),
}); });
const perform = () => ({}); const perform = () => ({});

View file

@ -9,10 +9,11 @@ import StickyBox from 'react-sticky-box/dist/esnext';
type Props = { type Props = {
subscriptions: Array<Subscription>, subscriptions: Array<Subscription>,
followedTags: Array<Tag>, followedTags: Array<Tag>,
email: ?string,
}; };
function SideBar(props: Props) { function SideBar(props: Props) {
const { subscriptions, followedTags } = props; const { subscriptions, followedTags, email } = props;
function buildLink(path, label, icon, guide) { function buildLink(path, label, icon, guide) {
return { return {
@ -25,47 +26,53 @@ function SideBar(props: Props) {
return ( return (
<StickyBox offsetTop={100} offsetBottom={20}> <StickyBox offsetTop={100} offsetBottom={20}>
<nav className="navigation"> {email ? (
<ul className="navigation-links"> <nav className="navigation">
{[ <ul className="navigation-links">
{ {[
...buildLink(null, __('Home'), ICONS.HOME), {
}, ...buildLink(null, __('Home'), ICONS.HOME),
{ },
...buildLink(PAGES.LIBRARY, __('Library'), ICONS.LIBRARY), // @if TARGET='app'
}, {
{ ...buildLink(PAGES.LIBRARY, __('Library'), ICONS.LIBRARY),
...buildLink(PAGES.PUBLISHED, __('Publishes'), ICONS.PUBLISH), },
}, // @endif
{ {
...buildLink(PAGES.FOLLOWING, __('Customize'), ICONS.EDIT), ...buildLink(PAGES.PUBLISHED, __('Publishes'), ICONS.PUBLISH),
}, },
].map(linkProps => ( {
<li key={linkProps.label}> ...buildLink(PAGES.FOLLOWING, __('Customize'), ICONS.EDIT),
<Button {...linkProps} className="navigation-link" activeClass="navigation-link--active" /> },
</li> ].map(linkProps => (
))} <li key={linkProps.label}>
</ul> <Button {...linkProps} className="navigation-link" activeClass="navigation-link--active" />
<ul className="navigation-links tags--vertical"> </li>
{followedTags.map(({ name }, key) => ( ))}
<li className="navigation-link__wrapper" key={name}> </ul>
<Tag navigate={`/$/tags?t${name}`} name={name} /> <ul className="navigation-links tags--vertical">
</li> {followedTags.map(({ name }, key) => (
))} <li className="navigation-link__wrapper" key={name}>
</ul> <Tag navigate={`/$/tags?t${name}`} name={name} />
<ul className="navigation-links--small"> </li>
{subscriptions.map(({ uri, channelName }, index) => ( ))}
<li key={uri} className="navigation-link__wrapper"> </ul>
<Button <ul className="navigation-links--small">
navigate={uri} {subscriptions.map(({ uri, channelName }, index) => (
label={channelName} <li key={uri} className="navigation-link__wrapper">
className="navigation-link" <Button
activeClass="navigation-link--active" navigate={uri}
/> label={channelName}
</li> className="navigation-link"
))} activeClass="navigation-link--active"
</ul> />
</nav> </li>
))}
</ul>
</nav>
) : (
<div className="navigation--placeholder" />
)}
</StickyBox> </StickyBox>
); );
} }

View file

@ -1,3 +1,4 @@
/* eslint-disable */
/* /*
This is taken from https://github.com/reactjs/react-autocomplete This is taken from https://github.com/reactjs/react-autocomplete
@ -208,7 +209,7 @@ export default class Autocomplete extends React.Component {
this.maybeAutoCompleteText = this.maybeAutoCompleteText.bind(this); this.maybeAutoCompleteText = this.maybeAutoCompleteText.bind(this);
} }
componentWillMount() { UNSAFE_componentWillMount() {
// this.refs is frozen, so we need to assign a new object to it // this.refs is frozen, so we need to assign a new object to it
this.refs = {}; this.refs = {};
this._ignoreBlur = false; this._ignoreBlur = false;
@ -222,7 +223,7 @@ export default class Autocomplete extends React.Component {
this._scrollTimer = null; this._scrollTimer = null;
} }
componentWillReceiveProps(nextProps) { UNSAFE_componentWillReceiveProps(nextProps) {
if (this.state.highlightedIndex !== null) { if (this.state.highlightedIndex !== null) {
this.setState(this.ensureHighlightedIndex); this.setState(this.ensureHighlightedIndex);
} }
@ -585,3 +586,4 @@ export default class Autocomplete extends React.Component {
); );
} }
} }
/* eslint-enable */

View file

@ -75,3 +75,4 @@ export const UNBLOCK = 'Circle';
export const VIEW = 'View'; export const VIEW = 'View';
export const EYE = 'Eye'; export const EYE = 'Eye';
export const EYE_OFF = 'EyeOff'; export const EYE_OFF = 'EyeOff';
export const SIGN_OUT = 'SignOut';

View file

@ -82,7 +82,7 @@ Lbryio.setOverride(
authToken = newAuthToken; authToken = newAuthToken;
// @if TARGET='web' // @if TARGET='web'
document.cookie = cookie.serialize('auth_token', authToken, { cookie.serialize('auth_token', authToken, {
maxAge: COOKIE_EXPIRE_TIME, maxAge: COOKIE_EXPIRE_TIME,
}); });
// @endif // @endif

View file

@ -1,14 +0,0 @@
import { connect } from 'react-redux';
import { doHideModal } from 'redux/actions/app';
import ModalAuthFailure from './view';
const select = () => ({});
const perform = dispatch => ({
close: () => dispatch(doHideModal()),
});
export default connect(
select,
perform
)(ModalAuthFailure);

View file

@ -1,32 +0,0 @@
// @flow
import React from 'react';
import { Modal } from 'modal/modal';
type Props = {
close: () => void,
};
class ModalAuthFailure extends React.PureComponent<Props> {
render() {
const { close } = this.props;
return (
<Modal
isOpen
contentLabel={__('Unable to Authenticate')}
title={__('Authentication Failure')}
type="confirm"
confirmButtonLabel={__('Reload')}
abortButtonLabel={__('Continue')}
onConfirmed={() => {
window.location.reload();
}}
onAborted={close}
>
<p>{__('If reloading does not fix this, or you see this at every start up, please email help@lbry.com.')}</p>
</Modal>
);
}
}
export default ModalAuthFailure;

View file

@ -2,7 +2,6 @@
import React from 'react'; import React from 'react';
import * as MODALS from 'constants/modal_types'; import * as MODALS from 'constants/modal_types';
import ModalError from 'modal/modalError'; import ModalError from 'modal/modalError';
import ModalAuthFailure from 'modal/modalAuthFailure';
import ModalDownloading from 'modal/modalDownloading'; import ModalDownloading from 'modal/modalDownloading';
import ModalAutoGenerateThumbnail from 'modal/modalAutoGenerateThumbnail'; import ModalAutoGenerateThumbnail from 'modal/modalAutoGenerateThumbnail';
import ModalAutoUpdateDownloaded from 'modal/modalAutoUpdateDownloaded'; import ModalAutoUpdateDownloaded from 'modal/modalAutoUpdateDownloaded';
@ -67,8 +66,6 @@ function ModalRouter(props: Props) {
return <ModalWelcome {...modalProps} />; return <ModalWelcome {...modalProps} />;
case MODALS.FIRST_REWARD: case MODALS.FIRST_REWARD:
return <ModalFirstReward {...modalProps} />; return <ModalFirstReward {...modalProps} />;
case MODALS.AUTHENTICATION_FAILURE:
return <ModalAuthFailure {...modalProps} />;
case MODALS.TRANSACTION_FAILED: case MODALS.TRANSACTION_FAILED:
return <ModalTransactionFailed {...modalProps} />; return <ModalTransactionFailed {...modalProps} />;
case MODALS.REWARD_APPROVAL_REQUIRED: case MODALS.REWARD_APPROVAL_REQUIRED:

View file

@ -1,7 +1,6 @@
// @flow // @flow
import * as ACTIONS from 'constants/action_types'; import * as ACTIONS from 'constants/action_types';
import * as MODALS from 'constants/modal_types';
import { remote } from 'electron'; import { remote } from 'electron';
// @if TARGET='app' // @if TARGET='app'
@ -251,14 +250,6 @@ reducers[ACTIONS.HIDE_MODAL] = state =>
modalProps: null, modalProps: null,
}); });
// This is fired from the lbryinc module
// Instead of adding callbacks in that module, we can just listen for this event
// There will be no other modals at this time as this is a blocking action
reducers[ACTIONS.AUTHENTICATION_FAILURE] = state =>
Object.assign({}, state, {
modal: MODALS.AUTHENTICATION_FAILURE,
});
reducers[ACTIONS.TOGGLE_SEARCH_EXPANDED] = state => reducers[ACTIONS.TOGGLE_SEARCH_EXPANDED] = state =>
Object.assign({}, state, { Object.assign({}, state, {
searchOptionsExpanded: !state.searchOptionsExpanded, searchOptionsExpanded: !state.searchOptionsExpanded,

View file

@ -67,3 +67,14 @@
width: 0; width: 0;
} }
} }
@keyframes menu-animate-in {
0% {
transform: scaleY(0);
opacity: 0;
}
100% {
transform: scaleY(1);
opacity: 1;
}
}

View file

@ -24,14 +24,13 @@
max-width: var(--page-max-width); max-width: var(--page-max-width);
height: calc(var(--header-height) - 1px); height: calc(var(--header-height) - 1px);
display: flex; display: flex;
justify-content: space-between;
margin: auto; margin: auto;
padding: 0 var(--spacing-large); padding: 0 var(--spacing-large);
} }
.header__navigation { .header__navigation {
flex: 1;
display: flex; display: flex;
justify-content: space-between;
&:last-of-type { &:last-of-type {
width: var(--side-nav-width); width: var(--side-nav-width);
@ -42,6 +41,14 @@
} }
} }
.header__menu {
width: var(--side-nav-width);
margin-left: auto;
display: flex;
justify-content: space-between;
align-items: center;
}
.header__navigation-arrows { .header__navigation-arrows {
display: flex; display: flex;
margin-right: var(--spacing-small); margin-right: var(--spacing-small);
@ -126,7 +133,6 @@
} }
.header__navigation-item--lbry { .header__navigation-item--lbry {
flex: 1;
font-weight: 800; font-weight: 800;
margin-right: var(--spacing-medium); margin-right: var(--spacing-medium);

View file

@ -1,13 +1,19 @@
.navigation { .navigation {
width: var(--side-nav-width); width: var(--side-nav-width);
font-size: var(--font-body); font-size: var(--font-body);
// padding-top: 100px;
@media (max-width: 600px) { @media (max-width: 600px) {
display: none; display: none;
} }
} }
.navigation--placeholder {
@extend .navigation;
height: 80vh;
background-color: $lbry-blue-1;
border-radius: var(--card-radius);
}
.navigation-links { .navigation-links {
@extend .ul--no-style; @extend .ul--no-style;
flex-direction: column; flex-direction: column;

View file

@ -1,10 +1,9 @@
.wunderbar { .wunderbar {
min-width: 175px; flex: 1;
height: 100%; height: 100%;
cursor: text; cursor: text;
display: flex; display: flex;
align-items: center; align-items: center;
flex: 1;
position: relative; position: relative;
z-index: 1; z-index: 1;
margin-right: var(--spacing-main-padding); margin-right: var(--spacing-main-padding);

View file

@ -17,16 +17,13 @@
white-space: nowrap; white-space: nowrap;
outline: none; outline: none;
background-color: $lbry-white; background-color: $lbry-white;
box-shadow: var(--card-box-shadow) $lbry-gray-2;
border: 1px solid $lbry-gray-1; border: 1px solid $lbry-gray-1;
border-top: none; border-top: none;
[data-mode='dark'] & { [data-mode='dark'] & {
background-color: var(--dm-color-05); background-color: var(--dm-color-05);
color: $lbry-white; color: $lbry-white;
box-shadow: var(--card-box-shadow) $lbry-black; border-color: var(--dm-color-03);
border: 1px solid $lbry-gray-5;
border-top: none;
} }
} }
@ -69,13 +66,14 @@
} }
} }
.menu__title { .menu__list--header {
&:not(:last-of-type) { margin-top: -1px;
margin-right: var(--spacing-medium); margin-left: calc(var(--spacing-medium) * -1);
} box-shadow: var(--card-box-shadow--attached) $lbry-gray-1;
animation: menu-animate-in var(--animation-duration) var(--animation-style);
span { [data-mode='dark'] & {
margin-left: var(--spacing-small); box-shadow: var(--card-box-shadow) $lbry-black;
} }
} }
@ -83,6 +81,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
padding: var(--spacing-medium); padding: var(--spacing-medium);
padding-right: var(--spacing-large);
} }
.menu__title, .menu__title,

View file

@ -74,7 +74,8 @@ $large-breakpoint: 1921px;
// Card // Card
--card-radius: 5px; --card-radius: 5px;
--card-max-width: 1000px; --card-max-width: 1000px;
--card-box-shadow: 0px 2px 6px 0; --card-box-shadow: 0px 2px 6px 0px;
--card-box-shadow--attached: 0px 4px 6px 0px;
// Modal // Modal
--modal-width: 440px; --modal-width: 440px;