Header refactor (#7440)

* Refactor header Component, split into smaller components and remove what is unused

* fix upload menu

* better centering for backout title

* cleanup

Co-authored-by: Rafael <rafael.saes@odysee.com>
This commit is contained in:
jessopb 2022-01-26 14:54:58 -05:00 committed by GitHub
parent 3f0cc0bf2e
commit f095081c71
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 440 additions and 438 deletions

View file

@ -2247,5 +2247,7 @@
"This support is priced in $USD.": "This support is priced in $USD.",
"The current exchange rate for the submitted LBC amount is ~ $%exchange_amount%.": "The current exchange rate for the submitted LBC amount is ~ $%exchange_amount%.",
"Amount of $%input_amount% LBC in USB is lower than price of $%price_amount%": "Amount of $%input_amount% LBC in USB is lower than price of $%price_amount%",
"Hosting for content you have downloaded": "Hosting for content you have downloaded",
"Hosting content selected by the network": "Hosting content selected by the network",
"--end--": "--end--"
}

View file

@ -0,0 +1,22 @@
// @flow
import React from 'react';
import { Link } from 'react-router-dom';
import { MenuLink } from '@reach/menu-button';
import Icon from 'component/common/icon';
type Props = {
icon: string,
name: string,
page: string,
};
export default function HeaderMenuLink(props: Props) {
const { icon, name, page } = props;
return (
<MenuLink className="menu__link" as={Link} to={`/$/${page}`}>
<Icon aria-hidden icon={icon} />
{name}
</MenuLink>
);
}

View file

@ -1,42 +1,31 @@
import * as MODALS from 'constants/modal_types';
import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux';
import { selectTotalBalance, selectBalance } from 'redux/selectors/wallet';
import { formatCredits } from 'util/format-credits';
import { selectGetSyncErrorMessage } from 'redux/selectors/sync';
import { selectUserVerifiedEmail, selectUserEmail, selectEmailToVerify, selectUser } from 'redux/selectors/user';
import { doClearEmailEntry, doClearPasswordEntry } from 'redux/actions/user';
import { doSetClientSetting } from 'redux/actions/settings';
import { doSignOut, doOpenModal } from 'redux/actions/app';
import { makeSelectClientSetting, selectLanguage } from 'redux/selectors/settings';
import { selectHasNavigated, selectActiveChannelClaim } from 'redux/selectors/app';
import { doSignOut } from 'redux/actions/app';
import { formatCredits } from 'util/format-credits';
import { selectClientSetting } from 'redux/selectors/settings';
import { selectGetSyncErrorMessage } from 'redux/selectors/sync';
import { selectHasNavigated } from 'redux/selectors/app';
import { selectTotalBalance, selectBalance } from 'redux/selectors/wallet';
import { selectUserVerifiedEmail, selectEmailToVerify, selectUser } from 'redux/selectors/user';
import * as SETTINGS from 'constants/settings';
import Header from './view';
import { selectMyChannelClaims } from 'redux/selectors/claims';
const select = (state) => ({
language: selectLanguage(state),
balance: selectBalance(state),
roundedSpendableBalance: formatCredits(selectBalance(state), 2, true),
roundedBalance: formatCredits(selectTotalBalance(state), 2, true),
currentTheme: makeSelectClientSetting(SETTINGS.THEME)(state),
automaticDarkModeEnabled: makeSelectClientSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED)(state),
hideBalance: makeSelectClientSetting(SETTINGS.HIDE_BALANCE)(state),
authenticated: selectUserVerifiedEmail(state),
email: selectUserEmail(state),
syncError: selectGetSyncErrorMessage(state),
balance: selectBalance(state),
emailToVerify: selectEmailToVerify(state),
hasNavigated: selectHasNavigated(state),
hideBalance: selectClientSetting(state, SETTINGS.HIDE_BALANCE),
roundedBalance: formatCredits(selectTotalBalance(state), 2, true),
roundedSpendableBalance: formatCredits(selectBalance(state), 2, true),
syncError: selectGetSyncErrorMessage(state),
user: selectUser(state),
activeChannelClaim: selectActiveChannelClaim(state),
myChannels: selectMyChannelClaims(state),
});
const perform = (dispatch) => ({
setClientSetting: (key, value, push) => dispatch(doSetClientSetting(key, value, push)),
signOut: () => dispatch(doSignOut()),
openSignOutModal: () => dispatch(doOpenModal(MODALS.SIGN_OUT)),
clearEmailEntry: () => dispatch(doClearEmailEntry()),
clearPasswordEntry: () => dispatch(doClearPasswordEntry()),
signOut: () => dispatch(doSignOut()),
});
export default connect(select, perform)(Header);

View file

@ -1,46 +1,22 @@
// @flow
import { ENABLE_UI_NOTIFICATIONS } from 'config';
import * as ICONS from 'constants/icons';
import * as SETTINGS from 'constants/settings';
import * as PAGES from 'constants/pages';
import React from 'react';
import { withRouter } from 'react-router';
import classnames from 'classnames';
import Button from 'component/button';
import WunderBar from 'component/wunderbar';
import Icon from 'component/common/icon';
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
import NavigationButton from 'component/navigationButton';
import { useIsMobile } from 'effects/use-screensize';
import NotificationBubble from 'component/notificationBubble';
import NotificationHeaderButton from 'component/notificationHeaderButton';
import ChannelThumbnail from 'component/channelThumbnail';
import SkipNavigationButton from 'component/skipNavigationButton';
import { withRouter } from 'react-router';
import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
import Button from 'component/button';
import classnames from 'classnames';
import HeaderMenuButtons from 'component/headerMenuButtons';
import HeaderProfileMenuButton from 'component/headerProfileMenuButton';
import Logo from 'component/logo';
import NotificationBubble from 'component/notificationBubble';
import React from 'react';
import SkipNavigationButton from 'component/skipNavigationButton';
import WunderBar from 'component/wunderbar';
import * as remote from '@electron/remote';
import { IS_MAC } from 'component/app/view';
import NavigationButton from 'component/navigationButton';
type Props = {
user: ?User,
balance: string,
balance: number,
roundedBalance: string,
roundedSpendableBalance: string,
history: {
entities: {}[],
goBack: () => void,
goForward: () => void,
index: number,
length: number,
location: { pathname: string },
push: (string) => void,
replace: (string) => void,
},
currentTheme: string,
automaticDarkModeEnabled: boolean,
setClientSetting: (string, boolean | string, ?boolean) => void,
hideBalance: boolean,
email: ?string,
authenticated: boolean,
authHeader: boolean,
backout: {
@ -49,147 +25,128 @@ type Props = {
title: string,
simpleTitle: string, // Just use the same value as `title` if `title` is already short (~< 10 chars), unless you have a better idea for title overlfow on mobile
},
syncError: ?string,
balance: number,
emailToVerify?: string,
signOut: () => void,
openSignOutModal: () => void,
hasNavigated: boolean,
hideBalance: boolean,
hideCancel: boolean,
history: {
goBack: () => void,
location: { pathname: string },
push: (string) => void,
replace: (string) => void,
},
isAbsoluteSideNavHidden: boolean,
roundedBalance: string,
roundedSpendableBalance: string,
sidebarOpen: boolean,
syncError: ?string,
clearEmailEntry: () => void,
clearPasswordEntry: () => void,
hasNavigated: boolean,
sidebarOpen: boolean,
setSidebarOpen: (boolean) => void,
isAbsoluteSideNavHidden: boolean,
hideCancel: boolean,
activeChannelClaim: ?ChannelClaim,
myChannels: ?Array<ChannelClaim>,
signOut: () => void,
};
const Header = (props: Props) => {
const {
balance,
roundedBalance,
roundedSpendableBalance,
history,
setClientSetting,
currentTheme,
automaticDarkModeEnabled,
hideBalance,
email,
authenticated,
authHeader,
signOut,
backout,
balance,
emailToVerify,
hideBalance,
hideCancel,
history,
isAbsoluteSideNavHidden,
roundedBalance,
roundedSpendableBalance,
sidebarOpen,
syncError,
openSignOutModal,
clearEmailEntry,
clearPasswordEntry,
emailToVerify,
backout,
sidebarOpen,
setSidebarOpen,
isAbsoluteSideNavHidden,
hideCancel,
user,
activeChannelClaim,
myChannels,
signOut,
} = props;
const {
location: { pathname },
goBack,
push,
} = history;
const isMobile = useIsMobile();
// 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);
const isSignUpPage = history.location.pathname.includes(PAGES.AUTH);
const isSignInPage = history.location.pathname.includes(PAGES.AUTH_SIGNIN);
const isPwdResetPage = history.location.pathname.includes(PAGES.AUTH_PASSWORD_RESET);
const hasBackout = Boolean(backout);
const isVerifyPage = pathname.includes(PAGES.AUTH_VERIFY);
const isSignUpPage = pathname.includes(PAGES.AUTH);
const isSignInPage = pathname.includes(PAGES.AUTH_SIGNIN);
const isPwdResetPage = pathname.includes(PAGES.AUTH_PASSWORD_RESET);
// For pages that allow for "backing out", shows a backout option instead of the Home logo
const canBackout = Boolean(backout);
const { backLabel, backNavDefault, title: backTitle, simpleTitle: simpleBackTitle } = backout || {};
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui);
const activeChannelUrl = activeChannelClaim && activeChannelClaim.permanent_url;
const hasChannels = myChannels && myChannels.length > 0;
// Sign out if they click the "x" when they are on the password prompt
const authHeaderAction = syncError ? { onClick: signOut } : { navigate: '/' };
const homeButtonNavigationProps = isVerifyPage ? {} : authHeader ? authHeaderAction : { navigate: '/' };
const closeButtonNavigationProps = {
onClick: () => {
clearEmailEntry();
clearPasswordEntry();
const authHeaderAction = syncError && { onClick: signOut };
const homeButtonNavigationProps = (isVerifyPage && {}) || (authHeader && authHeaderAction) || { navigate: '/' };
const sidebarLabel = sidebarOpen
? __('Close sidebar - hide channels you are following.')
: __('Expand sidebar - view channels you are following.');
if (syncError) {
signOut();
}
const onBackout = React.useCallback(
(e: any) => {
const { hasNavigated } = props;
const { replace } = history;
if (isSignInPage && !emailToVerify) {
history.goBack();
} else if (isSignUpPage) {
history.goBack();
} else if (isPwdResetPage) {
history.goBack();
} else {
history.push('/');
window.removeEventListener('popstate', onBackout);
if (e.type !== 'popstate') {
// if not initiated by pop (back button)
if (hasNavigated && !backNavDefault) {
goBack();
} else {
replace(backNavDefault || `/`);
}
}
},
};
function onBackout(e) {
const { history, hasNavigated } = props;
const { goBack, replace } = history;
window.removeEventListener('popstate', onBackout);
if (e.type !== 'popstate') {
// if not initiated by pop (back button)
if (hasNavigated && !backNavDefault) {
goBack();
} else {
replace(backNavDefault || `/`);
}
}
}
[backNavDefault, goBack, history, props]
);
React.useEffect(() => {
if (hasBackout) {
if (canBackout) {
window.addEventListener('popstate', onBackout);
return () => window.removeEventListener('popstate', onBackout);
}
}, [hasBackout]);
}, [canBackout, onBackout]);
function handleThemeToggle() {
if (automaticDarkModeEnabled) {
setClientSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, false);
}
const userButtons = (className: string) => (
<div className={classnames('header__menu', { 'header__menu--with-balance': authenticated })}>
<Button
title={
balance > 0
? __('Immediately spendable: %spendable_balance%', { spendable_balance: roundedSpendableBalance })
: __('Your Wallet')
}
navigate={`/$/${PAGES.WALLET}`}
className={classnames(className, 'header__navigation-item--balance')}
label={hideBalance || Number(roundedBalance) === 0 ? __('Your Wallet') : roundedBalance}
icon={ICONS.LBC}
// @if TARGET='app'
onDoubleClick={(e) => {
e.stopPropagation();
}}
// @endif
/>
if (currentTheme === 'dark') {
setClientSetting(SETTINGS.THEME, 'light', true);
} else {
setClientSetting(SETTINGS.THEME, 'dark', true);
}
}
type BalanceButtonProps = { className: string };
const BalanceButton = (balanceButtonProps: BalanceButtonProps) => (
<Button
title={
balance > 0
? __('Immediately spendable: %spendable_balance%', { spendable_balance: roundedSpendableBalance })
: __('Your Wallet')
}
navigate={`/$/${PAGES.WALLET}`}
className={classnames(balanceButtonProps.className, 'header__navigation-item--balance')}
label={hideBalance || Number(roundedBalance) === 0 ? __('Your Wallet') : roundedBalance}
icon={ICONS.LBC}
// @if TARGET='app'
onDoubleClick={(e) => {
e.stopPropagation();
}}
// @endif
/>
<HeaderProfileMenuButton />
</div>
);
return (
<header
className={classnames('header', {
'header--minimal': authHeader,
// @if TARGET='app'
'header--mac': IS_MAC,
// @endif
})}
// @if TARGET='app'
onDoubleClick={(e) => {
@ -198,38 +155,33 @@ const Header = (props: Props) => {
// @endif
>
<div className="header__contents">
{!authHeader && backout ? (
{!authHeader && canBackout ? (
<div className="card__actions--between">
<Button
onClick={onBackout}
button="link"
label={(backLabel && backLabel) || __('Cancel')}
icon={ICONS.ARROW_LEFT}
/>
{backTitle && <h1 className="header__auth-title">{isMobile ? simpleBackTitle || backTitle : backTitle}</h1>}
<BalanceButton className="header__navigation-item menu__title" />
<Button onClick={onBackout} button="link" label={backLabel || __('Cancel')} icon={ICONS.ARROW_LEFT} />
{backTitle && <h1 className="header__auth-title">{(isMobile && simpleBackTitle) || backTitle}</h1>}
{userButtons('header__navigation-item menu__title')}
</div>
) : (
<>
<div className="header__navigation">
<SkipNavigationButton />
{!authHeader && (
<span style={{ position: 'relative' }}>
<Button
aria-label={
sidebarOpen
? __('Close sidebar - hide channels you are following.')
: __('Expand sidebar - view channels you are following.')
}
aria-label={sidebarLabel}
className="header__navigation-item menu__title header__navigation-item--icon"
icon={ICONS.MENU}
aria-expanded={sidebarOpen}
onClick={() => setSidebarOpen(!sidebarOpen)}
>
{isAbsoluteSideNavHidden && isMobile && notificationsEnabled && <NotificationBubble />}
{isAbsoluteSideNavHidden && isMobile && <NotificationBubble />}
</Button>
</span>
)}
<Button
aria-label={__('Home')}
className="header__navigation-item header__navigation-item--lbry"
@ -256,100 +208,41 @@ const Header = (props: Props) => {
</div>
)}
{/* @endif */}
{!authHeader && <WunderBar />}
<HeaderMenuButtons
authenticated={authenticated}
notificationsEnabled={notificationsEnabled}
history={history}
handleThemeToggle={handleThemeToggle}
currentTheme={currentTheme}
/>
<WunderBar />
<HeaderMenuButtons />
</div>
)}
</div>
{!authHeader && !backout ? (
<div className={classnames('header__menu', 'header__menu--with-balance')}>
<BalanceButton className="header__navigation-item menu__title mobile-hidden" />
<Menu>
<MenuButton
aria-label={__('Your account')}
title={__('Your account')}
className={classnames('header__navigation-item', {
'menu__title header__navigation-item--icon': !activeChannelUrl,
'header__navigation-item--profile-pic': activeChannelUrl,
})}
// @if TARGET='app'
onDoubleClick={(e) => {
e.stopPropagation();
}}
// @endif
>
{activeChannelUrl ? (
<ChannelThumbnail uri={activeChannelUrl} small noLazyLoad />
) : (
<Icon size={18} icon={ICONS.ACCOUNT} aria-hidden />
)}
</MenuButton>
<MenuList className="menu__list--header">
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.UPLOADS}`)}>
<Icon aria-hidden icon={ICONS.PUBLISH} />
{__('Uploads')}
</MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.CHANNELS}`)}>
<Icon aria-hidden icon={ICONS.CHANNEL} />
{__('Channels')}
</MenuItem>
{hasChannels && authenticated && (
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.CREATOR_DASHBOARD}`)}>
<Icon aria-hidden icon={ICONS.ANALYTICS} />
{__('Creator Analytics')}
</MenuItem>
)}
{authenticated ? (
<MenuItem onSelect={openSignOutModal}>
<div className="menu__link">
<Icon aria-hidden icon={ICONS.SIGN_OUT} />
{__('Sign Out')}
</div>
<span className="menu__link-help">{email}</span>
</MenuItem>
) : (
<>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.AUTH_SIGNIN}`)}>
<Icon aria-hidden icon={ICONS.SIGN_IN} />
{__('Cloud Connect')}
</MenuItem>
</>
)}
</MenuList>
</Menu>
</div>
) : (
!isVerifyPage &&
!hideCancel && (
<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 />
{!authHeader && !canBackout
? userButtons('header__navigation-item menu__title mobile-hidden')
: !isVerifyPage &&
!hideCancel && (
<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 />
<Button
title={__('Go Back')}
button="alt"
// className="button--header-close"
icon={ICONS.REMOVE}
{...closeButtonNavigationProps}
// @if TARGET='app'
onDoubleClick={(e) => {
e.stopPropagation();
}}
// @endif
/>
</div>
)
)}
<Button
title={__('Go Back')}
button="alt"
// className="button--header-close"
icon={ICONS.REMOVE}
onClick={() => {
clearEmailEntry();
clearPasswordEntry();
if (syncError) signOut();
if ((isSignInPage && !emailToVerify) || isSignUpPage || isPwdResetPage) {
goBack();
} else {
push('/');
}
}}
/>
</div>
)}
</>
)}
</div>
@ -357,77 +250,4 @@ const Header = (props: Props) => {
);
};
type HeaderMenuButtonProps = {
authenticated: boolean,
notificationsEnabled: boolean,
history: { push: (string) => void },
handleThemeToggle: (string) => void,
currentTheme: string,
};
function HeaderMenuButtons(props: HeaderMenuButtonProps) {
const { notificationsEnabled, history, handleThemeToggle, currentTheme } = props;
return (
<div className="header__buttons">
<Menu>
<MenuButton
aria-label={__('Publish a file, or create a channel')}
title={__('Publish a file, or create a channel')}
className="header__navigation-item menu__title header__navigation-item--icon mobile-hidden"
// @if TARGET='app'
onDoubleClick={(e) => {
e.stopPropagation();
}}
// @endif
>
<Icon size={18} icon={ICONS.PUBLISH} aria-hidden />
</MenuButton>
<MenuList className="menu__list--header">
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.UPLOAD}`)}>
<Icon aria-hidden icon={ICONS.PUBLISH} />
{__('Upload')}
</MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.CHANNEL_NEW}`)}>
<Icon aria-hidden icon={ICONS.CHANNEL} />
{__('New Channel')}
</MenuItem>
</MenuList>
</Menu>
{notificationsEnabled && <NotificationHeaderButton />}
<Menu>
<MenuButton
aria-label={__('Settings')}
title={__('Settings')}
className="header__navigation-item menu__title header__navigation-item--icon mobile-hidden"
// @if TARGET='app'
onDoubleClick={(e) => {
e.stopPropagation();
}}
// @endif
>
<Icon size={18} icon={ICONS.SETTINGS} aria-hidden />
</MenuButton>
<MenuList className="menu__list--header">
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.SETTINGS}`)}>
<Icon aria-hidden tooltip icon={ICONS.SETTINGS} />
{__('Settings')}
</MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.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>
);
}
export default withRouter(Header);

View file

@ -0,0 +1,24 @@
import { connect } from 'react-redux';
import { doSetClientSetting } from 'redux/actions/settings';
import { selectActiveChannelStakedLevel } from 'redux/selectors/app';
import { selectClientSetting } from 'redux/selectors/settings';
import * as SETTINGS from 'constants/settings';
import HeaderMenuButtons from './view';
import { selectUserVerifiedEmail, selectUser } from 'redux/selectors/user';
const select = (state) => ({
activeChannelStakedLevel: selectActiveChannelStakedLevel(state),
authenticated: selectUserVerifiedEmail(state),
automaticDarkModeEnabled: selectClientSetting(state, SETTINGS.AUTOMATIC_DARK_MODE_ENABLED),
currentTheme: selectClientSetting(state, SETTINGS.THEME),
user: selectUser(state),
});
const perform = (dispatch) => ({
handleThemeToggle: (automaticDarkModeEnabled, currentTheme) => {
if (automaticDarkModeEnabled) dispatch(doSetClientSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, false));
dispatch(doSetClientSetting(SETTINGS.THEME, currentTheme === 'dark' ? 'light' : 'dark', true));
},
});
export default connect(select, perform)(HeaderMenuButtons);

View file

@ -0,0 +1,64 @@
// @flow
import { ENABLE_UI_NOTIFICATIONS } from 'config';
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
import HeaderMenuLink from 'component/common/header-menu-link';
import Icon from 'component/common/icon';
import NotificationHeaderButton from 'component/notificationHeaderButton';
import React from 'react';
type HeaderMenuButtonProps = {
authenticated: boolean,
automaticDarkModeEnabled: boolean,
currentTheme: string,
user: ?User,
handleThemeToggle: (boolean, string) => void,
};
export default function HeaderMenuButtons(props: HeaderMenuButtonProps) {
const { automaticDarkModeEnabled, currentTheme, user, handleThemeToggle } = props;
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui);
return (
<div className="header__buttons">
<Menu>
<MenuButton
aria-label={__('Publish a file, or create a channel')}
title={__('Publish a file, or create a channel')}
className="header__navigation-item menu__title header__navigation-item--icon mobile-hidden"
>
<Icon size={18} icon={ICONS.PUBLISH} aria-hidden />
</MenuButton>
<MenuList className="menu__list--header">
<HeaderMenuLink page={PAGES.UPLOAD} icon={ICONS.PUBLISH} name={__('Upload')} />
<HeaderMenuLink page={PAGES.CHANNEL_NEW} icon={ICONS.CHANNEL} name={__('New Channel')} />
</MenuList>
</Menu>
{notificationsEnabled && <NotificationHeaderButton />}
<Menu>
<MenuButton
aria-label={__('Settings')}
title={__('Settings')}
className="header__navigation-item menu__title header__navigation-item--icon mobile-hidden"
>
<Icon size={18} icon={ICONS.SETTINGS} aria-hidden />
</MenuButton>
<MenuList className="menu__list--header">
<HeaderMenuLink page={PAGES.SETTINGS} icon={ICONS.SETTINGS} name={__('Settings')} />
<HeaderMenuLink page={PAGES.HELP} icon={ICONS.HELP} name={__('Help')} />
<MenuItem className="menu__link" onSelect={() => handleThemeToggle(automaticDarkModeEnabled, currentTheme)}>
<Icon icon={currentTheme === 'light' ? ICONS.DARK : ICONS.LIGHT} />
{currentTheme === 'light' ? __('Dark') : __('Light')}
</MenuItem>
</MenuList>
</Menu>
</div>
);
}

View file

@ -0,0 +1,18 @@
import { connect } from 'react-redux';
import { doOpenModal } from 'redux/actions/app';
import { selectActiveChannelClaim } from 'redux/selectors/app';
import { selectUserEmail, selectUserVerifiedEmail } from 'redux/selectors/user';
import * as MODALS from 'constants/modal_types';
import HeaderProfileMenuButton from './view';
const select = (state) => ({
activeChannelClaim: selectActiveChannelClaim(state),
email: selectUserEmail(state),
authenticated: selectUserVerifiedEmail(state),
});
const perform = (dispatch) => ({
openSignOutModal: () => dispatch(doOpenModal(MODALS.SIGN_OUT)),
});
export default connect(select, perform)(HeaderProfileMenuButton);

View file

@ -0,0 +1,61 @@
// @flow
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
import ChannelThumbnail from 'component/channelThumbnail';
import classnames from 'classnames';
import HeaderMenuLink from 'component/common/header-menu-link';
import Icon from 'component/common/icon';
import React from 'react';
type HeaderMenuButtonProps = {
activeChannelClaim: ?ChannelClaim,
email: ?string,
authenticated: boolean,
openSignOutModal: () => void,
};
export default function HeaderProfileMenuButton(props: HeaderMenuButtonProps) {
const { activeChannelClaim, email, openSignOutModal, authenticated } = props;
const activeChannelUrl = activeChannelClaim && activeChannelClaim.permanent_url;
return (
<div className="header__buttons">
<Menu>
<MenuButton
aria-label={__('Your account')}
title={__('Your account')}
className={classnames('header__navigation-item', {
'menu__title header__navigation-item--icon': !activeChannelUrl,
'header__navigation-item--profile-pic': activeChannelUrl,
})}
>
{activeChannelUrl ? (
<ChannelThumbnail uri={activeChannelUrl} small noLazyLoad />
) : (
<Icon size={18} icon={ICONS.ACCOUNT} aria-hidden />
)}
</MenuButton>
<MenuList className="menu__list--header">
<HeaderMenuLink page={PAGES.UPLOADS} icon={ICONS.PUBLISH} name={__('Uploads')} />
<HeaderMenuLink page={PAGES.CHANNELS} icon={ICONS.CHANNEL} name={__('Channels')} />
<HeaderMenuLink page={PAGES.CREATOR_DASHBOARD} icon={ICONS.ANALYTICS} name={__('Creator Analytics')} />
{authenticated ? (
<MenuItem onSelect={openSignOutModal}>
<div className="menu__link">
<Icon aria-hidden icon={ICONS.SIGN_OUT} />
{__('Sign Out')}
</div>
<span className="menu__link-help">{email}</span>
</MenuItem>
) : (
<HeaderMenuLink page={PAGES.AUTH_SIGNIN} icon={ICONS.SIGN_IN} name={__('Cloud Connect')} />
)}
</MenuList>
</Menu>
</div>
);
}

View file

@ -72,6 +72,100 @@ import ModalWalletUnlock from 'modal/modalWalletUnlock';
import ModalYoutubeWelcome from 'modal/modalYoutubeWelcome';
function getModal(id) {
switch (id) {
case MODALS.UPGRADE:
return ModalUpgrade;
case MODALS.DOWNLOADING:
return ModalDownloading;
case MODALS.AUTO_GENERATE_THUMBNAIL:
return ModalAutoGenerateThumbnail;
case MODALS.AUTO_UPDATE_DOWNLOADED:
return ModalAutoUpdateDownloaded;
case MODALS.ERROR:
return ModalError;
case MODALS.FILE_TIMEOUT:
return ModalFileTimeout;
case MODALS.FIRST_REWARD:
return ModalFirstReward;
case MODALS.TRANSACTION_FAILED:
return ModalTransactionFailed;
case MODALS.CONFIRM_FILE_REMOVE:
return ModalRemoveFile;
case MODALS.AFFIRM_PURCHASE:
return ModalAffirmPurchase;
case MODALS.CONFIRM_CLAIM_REVOKE:
return ModalRevokeClaim;
case MODALS.PHONE_COLLECTION:
return ModalPhoneCollection;
case MODALS.FIRST_SUBSCRIPTION:
return ModalFirstSubscription;
case MODALS.SEND_TIP:
return ModalSendTip;
case MODALS.REPOST:
return ModalRepost;
case MODALS.SOCIAL_SHARE:
return ModalSocialShare;
case MODALS.PUBLISH:
return ModalPublish;
case MODALS.PUBLISH_PREVIEW:
return ModalPublishPreview;
case MODALS.CONFIRM_EXTERNAL_RESOURCE:
return ModalOpenExternalResource;
case MODALS.CONFIRM_TRANSACTION:
return ModalConfirmTransaction;
case MODALS.CONFIRM_THUMBNAIL_UPLOAD:
return ModalConfirmThumbnailUpload;
case MODALS.WALLET_ENCRYPT:
return ModalWalletEncrypt;
case MODALS.WALLET_DECRYPT:
return ModalWalletDecrypt;
case MODALS.WALLET_UNLOCK:
return ModalWalletUnlock;
case MODALS.WALLET_PASSWORD_UNSAVE:
return ModalPasswordUnsave;
case MODALS.REWARD_GENERATED_CODE:
return ModalRewardCode;
case MODALS.COMMENT_ACKNOWEDGEMENT:
return ModalCommentAcknowledgement;
case MODALS.YOUTUBE_WELCOME:
return ModalYoutubeWelcome;
case MODALS.SET_REFERRER:
return ModalSetReferrer;
case MODALS.SIGN_OUT:
return ModalSignOut;
case MODALS.CONFIRM_AGE:
return ModalConfirmAge;
case MODALS.FILE_SELECTION:
return ModalFileSelection;
case MODALS.LIQUIDATE_SUPPORTS:
return ModalSupportsLiquidate;
case MODALS.IMAGE_UPLOAD:
return ModalImageUpload;
case MODALS.SYNC_ENABLE:
return ModalSyncEnable;
case MODALS.MOBILE_SEARCH:
return ModalMobileSearch;
case MODALS.VIEW_IMAGE:
return ModalViewImage;
case MODALS.MASS_TIP_UNLOCK:
return ModalMassTipsUnlock;
case MODALS.CONFIRM_REMOVE_BTC_SWAP_ADDRESS:
return ModalRemoveBtcSwapAddress;
case MODALS.BLOCK_CHANNEL:
return ModalBlockChannel;
case MODALS.COLLECTION_ADD:
return ModalClaimCollectionAdd;
case MODALS.COLLECTION_DELETE:
return ModalDeleteCollection;
case MODALS.CONFIRM_REMOVE_CARD:
return ModalRemoveCard;
case MODALS.CONFIRM_REMOVE_COMMENT:
return ModalRemoveComment;
default:
return null;
}
}
type Props = {
modal: { id: string, modalProps: {} },
error: { message: string },
@ -94,102 +188,6 @@ function ModalRouter(props: Props) {
if (!modal) {
return null;
}
function getModal(id) {
switch (id) {
case MODALS.UPGRADE:
return ModalUpgrade;
case MODALS.DOWNLOADING:
return ModalDownloading;
case MODALS.AUTO_GENERATE_THUMBNAIL:
return ModalAutoGenerateThumbnail;
case MODALS.AUTO_UPDATE_DOWNLOADED:
return ModalAutoUpdateDownloaded;
case MODALS.ERROR:
return ModalError;
case MODALS.FILE_TIMEOUT:
return ModalFileTimeout;
case MODALS.FIRST_REWARD:
return ModalFirstReward;
case MODALS.TRANSACTION_FAILED:
return ModalTransactionFailed;
case MODALS.CONFIRM_FILE_REMOVE:
return ModalRemoveFile;
case MODALS.AFFIRM_PURCHASE:
return ModalAffirmPurchase;
case MODALS.CONFIRM_CLAIM_REVOKE:
return ModalRevokeClaim;
case MODALS.PHONE_COLLECTION:
return ModalPhoneCollection;
case MODALS.FIRST_SUBSCRIPTION:
return ModalFirstSubscription;
case MODALS.SEND_TIP:
return ModalSendTip;
case MODALS.REPOST:
return ModalRepost;
case MODALS.SOCIAL_SHARE:
return ModalSocialShare;
case MODALS.PUBLISH:
return ModalPublish;
case MODALS.PUBLISH_PREVIEW:
return ModalPublishPreview;
case MODALS.CONFIRM_EXTERNAL_RESOURCE:
return ModalOpenExternalResource;
case MODALS.CONFIRM_TRANSACTION:
return ModalConfirmTransaction;
case MODALS.CONFIRM_THUMBNAIL_UPLOAD:
return ModalConfirmThumbnailUpload;
case MODALS.WALLET_ENCRYPT:
return ModalWalletEncrypt;
case MODALS.WALLET_DECRYPT:
return ModalWalletDecrypt;
case MODALS.WALLET_UNLOCK:
return ModalWalletUnlock;
case MODALS.WALLET_PASSWORD_UNSAVE:
return ModalPasswordUnsave;
case MODALS.REWARD_GENERATED_CODE:
return ModalRewardCode;
case MODALS.COMMENT_ACKNOWEDGEMENT:
return ModalCommentAcknowledgement;
case MODALS.YOUTUBE_WELCOME:
return ModalYoutubeWelcome;
case MODALS.SET_REFERRER:
return ModalSetReferrer;
case MODALS.SIGN_OUT:
return ModalSignOut;
case MODALS.CONFIRM_AGE:
return ModalConfirmAge;
case MODALS.FILE_SELECTION:
return ModalFileSelection;
case MODALS.LIQUIDATE_SUPPORTS:
return ModalSupportsLiquidate;
case MODALS.IMAGE_UPLOAD:
return ModalImageUpload;
case MODALS.SYNC_ENABLE:
return ModalSyncEnable;
case MODALS.MOBILE_SEARCH:
return ModalMobileSearch;
case MODALS.VIEW_IMAGE:
return ModalViewImage;
case MODALS.MASS_TIP_UNLOCK:
return ModalMassTipsUnlock;
case MODALS.CONFIRM_REMOVE_BTC_SWAP_ADDRESS:
return ModalRemoveBtcSwapAddress;
case MODALS.BLOCK_CHANNEL:
return ModalBlockChannel;
case MODALS.COLLECTION_ADD:
return ModalClaimCollectionAdd;
case MODALS.COLLECTION_DELETE:
return ModalDeleteCollection;
case MODALS.CONFIRM_REMOVE_CARD:
return ModalRemoveCard;
case MODALS.CONFIRM_REMOVE_COMMENT:
return ModalRemoveComment;
default:
return null;
}
}
const { id, modalProps } = modal;
const SelectedModal = getModal(id);

View file

@ -20,6 +20,11 @@ export const selectClientSettings = createSelector(selectState, (state) => state
export const selectLoadedLanguages = createSelector(selectState, (state) => state.loadedLanguages || {});
export const selectClientSetting = (state, setting) => {
const clientSettings = selectClientSettings(state);
return clientSettings ? clientSettings[setting] : undefined;
};
export const makeSelectClientSetting = (setting) =>
createSelector(selectClientSettings, (settings) => (settings ? settings[setting] : undefined));

View file

@ -61,7 +61,6 @@
}
.header__menu {
margin-left: auto;
display: flex;
justify-content: space-between;
align-items: center;