From feee3d827d4c99a8c692b198c64f375ceda8152b Mon Sep 17 00:00:00 2001 From: Rafael <rafael.saes@odysee.com> Date: Mon, 20 Dec 2021 09:29:51 -0300 Subject: [PATCH 1/4] Refactor header Component, split into smaller components and remove what is unused --- static/app-strings.json | 2 + ui/component/common/header-menu-link.jsx | 22 + ui/component/header/index.js | 39 +- ui/component/header/view.jsx | 452 ++++++------------ ui/component/headerMenuButtons/index.js | 24 + ui/component/headerMenuButtons/view.jsx | 67 +++ ui/component/headerProfileMenuButton/index.js | 18 + ui/component/headerProfileMenuButton/view.jsx | 61 +++ ui/modal/modalRouter/view.jsx | 213 +++++---- ui/redux/selectors/settings.js | 5 + 10 files changed, 466 insertions(+), 437 deletions(-) create mode 100644 ui/component/common/header-menu-link.jsx create mode 100644 ui/component/headerMenuButtons/index.js create mode 100644 ui/component/headerMenuButtons/view.jsx create mode 100644 ui/component/headerProfileMenuButton/index.js create mode 100644 ui/component/headerProfileMenuButton/view.jsx diff --git a/static/app-strings.json b/static/app-strings.json index 6e93c4912..0e1d72689 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -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--" } diff --git a/ui/component/common/header-menu-link.jsx b/ui/component/common/header-menu-link.jsx new file mode 100644 index 000000000..c5c14d385 --- /dev/null +++ b/ui/component/common/header-menu-link.jsx @@ -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> + ); +} diff --git a/ui/component/header/index.js b/ui/component/header/index.js index a320ff346..34835dbd5 100644 --- a/ui/component/header/index.js +++ b/ui/component/header/index.js @@ -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); diff --git a/ui/component/header/view.jsx b/ui/component/header/view.jsx index 9026ded84..fe919a4f6 100644 --- a/ui/component/header/view.jsx +++ b/ui/component/header/view.jsx @@ -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); diff --git a/ui/component/headerMenuButtons/index.js b/ui/component/headerMenuButtons/index.js new file mode 100644 index 000000000..dab91821b --- /dev/null +++ b/ui/component/headerMenuButtons/index.js @@ -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); diff --git a/ui/component/headerMenuButtons/view.jsx b/ui/component/headerMenuButtons/view.jsx new file mode 100644 index 000000000..9777af20c --- /dev/null +++ b/ui/component/headerMenuButtons/view.jsx @@ -0,0 +1,67 @@ +// @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 { authenticated, automaticDarkModeEnabled, currentTheme, user, handleThemeToggle } = props; + + const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui); + + return ( + <div className="header__buttons"> + {authenticated && ( + <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')} /> + <HeaderMenuLink page={PAGES.YOUTUBE_SYNC} icon={ICONS.YOUTUBE} name={__('Sync YouTube 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> + ); +} diff --git a/ui/component/headerProfileMenuButton/index.js b/ui/component/headerProfileMenuButton/index.js new file mode 100644 index 000000000..fd0afb821 --- /dev/null +++ b/ui/component/headerProfileMenuButton/index.js @@ -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); diff --git a/ui/component/headerProfileMenuButton/view.jsx b/ui/component/headerProfileMenuButton/view.jsx new file mode 100644 index 000000000..1e35de7ac --- /dev/null +++ b/ui/component/headerProfileMenuButton/view.jsx @@ -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> + ); +} diff --git a/ui/modal/modalRouter/view.jsx b/ui/modal/modalRouter/view.jsx index 353d39e22..43ea15124 100644 --- a/ui/modal/modalRouter/view.jsx +++ b/ui/modal/modalRouter/view.jsx @@ -72,6 +72,123 @@ import ModalWalletUnlock from 'modal/modalWalletUnlock'; import ModalYoutubeWelcome from 'modal/modalYoutubeWelcome'; +// 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; +// } + +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 +211,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); diff --git a/ui/redux/selectors/settings.js b/ui/redux/selectors/settings.js index 0cf497fba..5b759cc5d 100644 --- a/ui/redux/selectors/settings.js +++ b/ui/redux/selectors/settings.js @@ -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)); -- 2.45.3 From 2d0c3c2c6025330e6fa3928f1ac36d11c95af78d Mon Sep 17 00:00:00 2001 From: zeppi <jessopb@gmail.com> Date: Wed, 26 Jan 2022 11:53:22 -0500 Subject: [PATCH 2/4] fix upload menu --- ui/component/headerMenuButtons/view.jsx | 31 +++++++++++-------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/ui/component/headerMenuButtons/view.jsx b/ui/component/headerMenuButtons/view.jsx index 9777af20c..d315feb64 100644 --- a/ui/component/headerMenuButtons/view.jsx +++ b/ui/component/headerMenuButtons/view.jsx @@ -17,29 +17,26 @@ type HeaderMenuButtonProps = { }; export default function HeaderMenuButtons(props: HeaderMenuButtonProps) { - const { authenticated, automaticDarkModeEnabled, currentTheme, user, handleThemeToggle } = props; + const { automaticDarkModeEnabled, currentTheme, user, handleThemeToggle } = props; const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui); return ( <div className="header__buttons"> - {authenticated && ( - <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> + <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')} /> - <HeaderMenuLink page={PAGES.YOUTUBE_SYNC} icon={ICONS.YOUTUBE} name={__('Sync YouTube Channel')} /> - </MenuList> - </Menu> - )} + <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 />} -- 2.45.3 From f3588438ec6403b2074f0aa372ace462496920f6 Mon Sep 17 00:00:00 2001 From: zeppi <jessopb@gmail.com> Date: Wed, 26 Jan 2022 14:17:52 -0500 Subject: [PATCH 3/4] better centering for backout title --- ui/scss/component/_header.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/scss/component/_header.scss b/ui/scss/component/_header.scss index 0c2044388..906ec911d 100644 --- a/ui/scss/component/_header.scss +++ b/ui/scss/component/_header.scss @@ -61,7 +61,6 @@ } .header__menu { - margin-left: auto; display: flex; justify-content: space-between; align-items: center; -- 2.45.3 From 733f781a913890aacf0a9d9fb8076a1f5b367545 Mon Sep 17 00:00:00 2001 From: zeppi <jessopb@gmail.com> Date: Wed, 26 Jan 2022 14:50:34 -0500 Subject: [PATCH 4/4] cleanup --- ui/modal/modalRouter/view.jsx | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/ui/modal/modalRouter/view.jsx b/ui/modal/modalRouter/view.jsx index 43ea15124..7ce9ba59d 100644 --- a/ui/modal/modalRouter/view.jsx +++ b/ui/modal/modalRouter/view.jsx @@ -72,29 +72,6 @@ import ModalWalletUnlock from 'modal/modalWalletUnlock'; import ModalYoutubeWelcome from 'modal/modalYoutubeWelcome'; -// 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; -// } - function getModal(id) { switch (id) { case MODALS.UPGRADE: -- 2.45.3