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:
parent
3f0cc0bf2e
commit
f095081c71
11 changed files with 440 additions and 438 deletions
|
@ -2247,5 +2247,7 @@
|
||||||
"This support is priced in $USD.": "This support is priced in $USD.",
|
"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%.",
|
"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%",
|
"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--"
|
"--end--": "--end--"
|
||||||
}
|
}
|
||||||
|
|
22
ui/component/common/header-menu-link.jsx
Normal file
22
ui/component/common/header-menu-link.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,42 +1,31 @@
|
||||||
import * as MODALS from 'constants/modal_types';
|
|
||||||
import * as SETTINGS from 'constants/settings';
|
|
||||||
import { connect } from 'react-redux';
|
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 { doClearEmailEntry, doClearPasswordEntry } from 'redux/actions/user';
|
||||||
import { doSetClientSetting } from 'redux/actions/settings';
|
import { doSignOut } from 'redux/actions/app';
|
||||||
import { doSignOut, doOpenModal } from 'redux/actions/app';
|
import { formatCredits } from 'util/format-credits';
|
||||||
import { makeSelectClientSetting, selectLanguage } from 'redux/selectors/settings';
|
import { selectClientSetting } from 'redux/selectors/settings';
|
||||||
import { selectHasNavigated, selectActiveChannelClaim } from 'redux/selectors/app';
|
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 Header from './view';
|
||||||
import { selectMyChannelClaims } from 'redux/selectors/claims';
|
|
||||||
|
|
||||||
const select = (state) => ({
|
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),
|
authenticated: selectUserVerifiedEmail(state),
|
||||||
email: selectUserEmail(state),
|
balance: selectBalance(state),
|
||||||
syncError: selectGetSyncErrorMessage(state),
|
|
||||||
emailToVerify: selectEmailToVerify(state),
|
emailToVerify: selectEmailToVerify(state),
|
||||||
hasNavigated: selectHasNavigated(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),
|
user: selectUser(state),
|
||||||
activeChannelClaim: selectActiveChannelClaim(state),
|
|
||||||
myChannels: selectMyChannelClaims(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
setClientSetting: (key, value, push) => dispatch(doSetClientSetting(key, value, push)),
|
|
||||||
signOut: () => dispatch(doSignOut()),
|
|
||||||
openSignOutModal: () => dispatch(doOpenModal(MODALS.SIGN_OUT)),
|
|
||||||
clearEmailEntry: () => dispatch(doClearEmailEntry()),
|
clearEmailEntry: () => dispatch(doClearEmailEntry()),
|
||||||
clearPasswordEntry: () => dispatch(doClearPasswordEntry()),
|
clearPasswordEntry: () => dispatch(doClearPasswordEntry()),
|
||||||
|
signOut: () => dispatch(doSignOut()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(Header);
|
export default connect(select, perform)(Header);
|
||||||
|
|
|
@ -1,46 +1,22 @@
|
||||||
// @flow
|
// @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 { useIsMobile } from 'effects/use-screensize';
|
||||||
import NotificationBubble from 'component/notificationBubble';
|
import { withRouter } from 'react-router';
|
||||||
import NotificationHeaderButton from 'component/notificationHeaderButton';
|
import * as ICONS from 'constants/icons';
|
||||||
import ChannelThumbnail from 'component/channelThumbnail';
|
import * as PAGES from 'constants/pages';
|
||||||
import SkipNavigationButton from 'component/skipNavigationButton';
|
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 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 * as remote from '@electron/remote';
|
||||||
import { IS_MAC } from 'component/app/view';
|
import { IS_MAC } from 'component/app/view';
|
||||||
|
import NavigationButton from 'component/navigationButton';
|
||||||
|
|
||||||
type Props = {
|
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,
|
authenticated: boolean,
|
||||||
authHeader: boolean,
|
authHeader: boolean,
|
||||||
backout: {
|
backout: {
|
||||||
|
@ -49,88 +25,78 @@ type Props = {
|
||||||
title: string,
|
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
|
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,
|
emailToVerify?: string,
|
||||||
signOut: () => void,
|
hasNavigated: boolean,
|
||||||
openSignOutModal: () => void,
|
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,
|
clearEmailEntry: () => void,
|
||||||
clearPasswordEntry: () => void,
|
clearPasswordEntry: () => void,
|
||||||
hasNavigated: boolean,
|
|
||||||
sidebarOpen: boolean,
|
|
||||||
setSidebarOpen: (boolean) => void,
|
setSidebarOpen: (boolean) => void,
|
||||||
isAbsoluteSideNavHidden: boolean,
|
signOut: () => void,
|
||||||
hideCancel: boolean,
|
|
||||||
activeChannelClaim: ?ChannelClaim,
|
|
||||||
myChannels: ?Array<ChannelClaim>,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Header = (props: Props) => {
|
const Header = (props: Props) => {
|
||||||
const {
|
const {
|
||||||
balance,
|
|
||||||
roundedBalance,
|
|
||||||
roundedSpendableBalance,
|
|
||||||
history,
|
|
||||||
setClientSetting,
|
|
||||||
currentTheme,
|
|
||||||
automaticDarkModeEnabled,
|
|
||||||
hideBalance,
|
|
||||||
email,
|
|
||||||
authenticated,
|
authenticated,
|
||||||
authHeader,
|
authHeader,
|
||||||
signOut,
|
backout,
|
||||||
|
balance,
|
||||||
|
emailToVerify,
|
||||||
|
hideBalance,
|
||||||
|
hideCancel,
|
||||||
|
history,
|
||||||
|
isAbsoluteSideNavHidden,
|
||||||
|
roundedBalance,
|
||||||
|
roundedSpendableBalance,
|
||||||
|
sidebarOpen,
|
||||||
syncError,
|
syncError,
|
||||||
openSignOutModal,
|
|
||||||
clearEmailEntry,
|
clearEmailEntry,
|
||||||
clearPasswordEntry,
|
clearPasswordEntry,
|
||||||
emailToVerify,
|
|
||||||
backout,
|
|
||||||
sidebarOpen,
|
|
||||||
setSidebarOpen,
|
setSidebarOpen,
|
||||||
isAbsoluteSideNavHidden,
|
signOut,
|
||||||
hideCancel,
|
|
||||||
user,
|
|
||||||
activeChannelClaim,
|
|
||||||
myChannels,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
location: { pathname },
|
||||||
|
goBack,
|
||||||
|
push,
|
||||||
|
} = history;
|
||||||
|
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
// on the verify page don't let anyone escape other than by closing the tab to keep session data consistent
|
// 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 isVerifyPage = pathname.includes(PAGES.AUTH_VERIFY);
|
||||||
const isSignUpPage = history.location.pathname.includes(PAGES.AUTH);
|
const isSignUpPage = pathname.includes(PAGES.AUTH);
|
||||||
const isSignInPage = history.location.pathname.includes(PAGES.AUTH_SIGNIN);
|
const isSignInPage = pathname.includes(PAGES.AUTH_SIGNIN);
|
||||||
const isPwdResetPage = history.location.pathname.includes(PAGES.AUTH_PASSWORD_RESET);
|
const isPwdResetPage = pathname.includes(PAGES.AUTH_PASSWORD_RESET);
|
||||||
const hasBackout = Boolean(backout);
|
|
||||||
|
// 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 { 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
|
// Sign out if they click the "x" when they are on the password prompt
|
||||||
const authHeaderAction = syncError ? { onClick: signOut } : { navigate: '/' };
|
const authHeaderAction = syncError && { onClick: signOut };
|
||||||
const homeButtonNavigationProps = isVerifyPage ? {} : authHeader ? authHeaderAction : { navigate: '/' };
|
const homeButtonNavigationProps = (isVerifyPage && {}) || (authHeader && authHeaderAction) || { navigate: '/' };
|
||||||
const closeButtonNavigationProps = {
|
const sidebarLabel = sidebarOpen
|
||||||
onClick: () => {
|
? __('Close sidebar - hide channels you are following.')
|
||||||
clearEmailEntry();
|
: __('Expand sidebar - view channels you are following.');
|
||||||
clearPasswordEntry();
|
|
||||||
|
|
||||||
if (syncError) {
|
const onBackout = React.useCallback(
|
||||||
signOut();
|
(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('/');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function onBackout(e) {
|
|
||||||
const { history, hasNavigated } = props;
|
|
||||||
const { goBack, replace } = history;
|
|
||||||
|
|
||||||
window.removeEventListener('popstate', onBackout);
|
window.removeEventListener('popstate', onBackout);
|
||||||
|
|
||||||
|
@ -142,29 +108,19 @@ const Header = (props: Props) => {
|
||||||
replace(backNavDefault || `/`);
|
replace(backNavDefault || `/`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
[backNavDefault, goBack, history, props]
|
||||||
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (hasBackout) {
|
if (canBackout) {
|
||||||
window.addEventListener('popstate', onBackout);
|
window.addEventListener('popstate', onBackout);
|
||||||
return () => window.removeEventListener('popstate', onBackout);
|
return () => window.removeEventListener('popstate', onBackout);
|
||||||
}
|
}
|
||||||
}, [hasBackout]);
|
}, [canBackout, onBackout]);
|
||||||
|
|
||||||
function handleThemeToggle() {
|
const userButtons = (className: string) => (
|
||||||
if (automaticDarkModeEnabled) {
|
<div className={classnames('header__menu', { 'header__menu--with-balance': authenticated })}>
|
||||||
setClientSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentTheme === 'dark') {
|
|
||||||
setClientSetting(SETTINGS.THEME, 'light', true);
|
|
||||||
} else {
|
|
||||||
setClientSetting(SETTINGS.THEME, 'dark', true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type BalanceButtonProps = { className: string };
|
|
||||||
const BalanceButton = (balanceButtonProps: BalanceButtonProps) => (
|
|
||||||
<Button
|
<Button
|
||||||
title={
|
title={
|
||||||
balance > 0
|
balance > 0
|
||||||
|
@ -172,7 +128,7 @@ const Header = (props: Props) => {
|
||||||
: __('Your Wallet')
|
: __('Your Wallet')
|
||||||
}
|
}
|
||||||
navigate={`/$/${PAGES.WALLET}`}
|
navigate={`/$/${PAGES.WALLET}`}
|
||||||
className={classnames(balanceButtonProps.className, 'header__navigation-item--balance')}
|
className={classnames(className, 'header__navigation-item--balance')}
|
||||||
label={hideBalance || Number(roundedBalance) === 0 ? __('Your Wallet') : roundedBalance}
|
label={hideBalance || Number(roundedBalance) === 0 ? __('Your Wallet') : roundedBalance}
|
||||||
icon={ICONS.LBC}
|
icon={ICONS.LBC}
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
|
@ -181,15 +137,16 @@ const Header = (props: Props) => {
|
||||||
}}
|
}}
|
||||||
// @endif
|
// @endif
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<HeaderProfileMenuButton />
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className={classnames('header', {
|
className={classnames('header', {
|
||||||
'header--minimal': authHeader,
|
'header--minimal': authHeader,
|
||||||
// @if TARGET='app'
|
|
||||||
'header--mac': IS_MAC,
|
'header--mac': IS_MAC,
|
||||||
// @endif
|
|
||||||
})}
|
})}
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
onDoubleClick={(e) => {
|
onDoubleClick={(e) => {
|
||||||
|
@ -198,38 +155,33 @@ const Header = (props: Props) => {
|
||||||
// @endif
|
// @endif
|
||||||
>
|
>
|
||||||
<div className="header__contents">
|
<div className="header__contents">
|
||||||
{!authHeader && backout ? (
|
{!authHeader && canBackout ? (
|
||||||
<div className="card__actions--between">
|
<div className="card__actions--between">
|
||||||
<Button
|
<Button onClick={onBackout} button="link" label={backLabel || __('Cancel')} icon={ICONS.ARROW_LEFT} />
|
||||||
onClick={onBackout}
|
|
||||||
button="link"
|
{backTitle && <h1 className="header__auth-title">{(isMobile && simpleBackTitle) || backTitle}</h1>}
|
||||||
label={(backLabel && backLabel) || __('Cancel')}
|
|
||||||
icon={ICONS.ARROW_LEFT}
|
{userButtons('header__navigation-item menu__title')}
|
||||||
/>
|
|
||||||
{backTitle && <h1 className="header__auth-title">{isMobile ? simpleBackTitle || backTitle : backTitle}</h1>}
|
|
||||||
<BalanceButton className="header__navigation-item menu__title" />
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="header__navigation">
|
<div className="header__navigation">
|
||||||
<SkipNavigationButton />
|
<SkipNavigationButton />
|
||||||
|
|
||||||
{!authHeader && (
|
{!authHeader && (
|
||||||
<span style={{ position: 'relative' }}>
|
<span style={{ position: 'relative' }}>
|
||||||
<Button
|
<Button
|
||||||
aria-label={
|
aria-label={sidebarLabel}
|
||||||
sidebarOpen
|
|
||||||
? __('Close sidebar - hide channels you are following.')
|
|
||||||
: __('Expand sidebar - view channels you are following.')
|
|
||||||
}
|
|
||||||
className="header__navigation-item menu__title header__navigation-item--icon"
|
className="header__navigation-item menu__title header__navigation-item--icon"
|
||||||
icon={ICONS.MENU}
|
icon={ICONS.MENU}
|
||||||
aria-expanded={sidebarOpen}
|
aria-expanded={sidebarOpen}
|
||||||
onClick={() => setSidebarOpen(!sidebarOpen)}
|
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||||
>
|
>
|
||||||
{isAbsoluteSideNavHidden && isMobile && notificationsEnabled && <NotificationBubble />}
|
{isAbsoluteSideNavHidden && isMobile && <NotificationBubble />}
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
aria-label={__('Home')}
|
aria-label={__('Home')}
|
||||||
className="header__navigation-item header__navigation-item--lbry"
|
className="header__navigation-item header__navigation-item--lbry"
|
||||||
|
@ -256,79 +208,15 @@ const Header = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* @endif */}
|
{/* @endif */}
|
||||||
|
<WunderBar />
|
||||||
{!authHeader && <WunderBar />}
|
<HeaderMenuButtons />
|
||||||
|
|
||||||
<HeaderMenuButtons
|
|
||||||
authenticated={authenticated}
|
|
||||||
notificationsEnabled={notificationsEnabled}
|
|
||||||
history={history}
|
|
||||||
handleThemeToggle={handleThemeToggle}
|
|
||||||
currentTheme={currentTheme}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!authHeader && !backout ? (
|
{!authHeader && !canBackout
|
||||||
<div className={classnames('header__menu', 'header__menu--with-balance')}>
|
? userButtons('header__navigation-item menu__title mobile-hidden')
|
||||||
<BalanceButton className="header__navigation-item menu__title mobile-hidden" />
|
: !isVerifyPage &&
|
||||||
<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 && (
|
!hideCancel && (
|
||||||
<div className="header__menu">
|
<div className="header__menu">
|
||||||
{/* Add an empty span here so we can use the same style as above */}
|
{/* Add an empty span here so we can use the same style as above */}
|
||||||
|
@ -340,15 +228,20 @@ const Header = (props: Props) => {
|
||||||
button="alt"
|
button="alt"
|
||||||
// className="button--header-close"
|
// className="button--header-close"
|
||||||
icon={ICONS.REMOVE}
|
icon={ICONS.REMOVE}
|
||||||
{...closeButtonNavigationProps}
|
onClick={() => {
|
||||||
// @if TARGET='app'
|
clearEmailEntry();
|
||||||
onDoubleClick={(e) => {
|
clearPasswordEntry();
|
||||||
e.stopPropagation();
|
|
||||||
|
if (syncError) signOut();
|
||||||
|
|
||||||
|
if ((isSignInPage && !emailToVerify) || isSignUpPage || isPwdResetPage) {
|
||||||
|
goBack();
|
||||||
|
} else {
|
||||||
|
push('/');
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
// @endif
|
|
||||||
/>
|
/>
|
||||||
</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);
|
export default withRouter(Header);
|
||||||
|
|
24
ui/component/headerMenuButtons/index.js
Normal file
24
ui/component/headerMenuButtons/index.js
Normal 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);
|
64
ui/component/headerMenuButtons/view.jsx
Normal file
64
ui/component/headerMenuButtons/view.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
18
ui/component/headerProfileMenuButton/index.js
Normal file
18
ui/component/headerProfileMenuButton/index.js
Normal 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);
|
61
ui/component/headerProfileMenuButton/view.jsx
Normal file
61
ui/component/headerProfileMenuButton/view.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -72,30 +72,7 @@ import ModalWalletUnlock from 'modal/modalWalletUnlock';
|
||||||
|
|
||||||
import ModalYoutubeWelcome from 'modal/modalYoutubeWelcome';
|
import ModalYoutubeWelcome from 'modal/modalYoutubeWelcome';
|
||||||
|
|
||||||
type Props = {
|
function getModal(id) {
|
||||||
modal: { id: string, modalProps: {} },
|
|
||||||
error: { message: string },
|
|
||||||
location: { pathname: string },
|
|
||||||
hideModal: () => void,
|
|
||||||
};
|
|
||||||
|
|
||||||
function ModalRouter(props: Props) {
|
|
||||||
const { modal, error, location, hideModal } = props;
|
|
||||||
const { pathname } = location;
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
hideModal();
|
|
||||||
}, [pathname, hideModal]);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return <ModalError {...error} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!modal) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getModal(id) {
|
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case MODALS.UPGRADE:
|
case MODALS.UPGRADE:
|
||||||
return ModalUpgrade;
|
return ModalUpgrade;
|
||||||
|
@ -188,8 +165,29 @@ function ModalRouter(props: Props) {
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
type Props = {
|
||||||
|
modal: { id: string, modalProps: {} },
|
||||||
|
error: { message: string },
|
||||||
|
location: { pathname: string },
|
||||||
|
hideModal: () => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
function ModalRouter(props: Props) {
|
||||||
|
const { modal, error, location, hideModal } = props;
|
||||||
|
const { pathname } = location;
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
hideModal();
|
||||||
|
}, [pathname, hideModal]);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <ModalError {...error} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!modal) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const { id, modalProps } = modal;
|
const { id, modalProps } = modal;
|
||||||
const SelectedModal = getModal(id);
|
const SelectedModal = getModal(id);
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,11 @@ export const selectClientSettings = createSelector(selectState, (state) => state
|
||||||
|
|
||||||
export const selectLoadedLanguages = createSelector(selectState, (state) => state.loadedLanguages || {});
|
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) =>
|
export const makeSelectClientSetting = (setting) =>
|
||||||
createSelector(selectClientSettings, (settings) => (settings ? settings[setting] : undefined));
|
createSelector(selectClientSettings, (settings) => (settings ? settings[setting] : undefined));
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.header__menu {
|
.header__menu {
|
||||||
margin-left: auto;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
Loading…
Reference in a new issue