@worldresources/gfw-components
Version:
React component library for the Global Forest Watch project.
323 lines (302 loc) • 11 kB
JavaScript
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import OutsideClickHandler from 'react-outside-click-handler';
import qs from 'query-string';
import moment from 'moment';
import { checkLoggedIn } from 'services/user';
import { Media, MediaContextProvider } from 'components/responsive';
import { APP_URL } from 'constants';
import gfwLogo from 'assets/logos/gfw.png';
import MenuIcon from 'assets/icons/menu.svg';
import CloseIcon from 'assets/icons/close.svg';
import NavLink from 'components/header/components/nav-link';
import Row from 'components/grid/row';
import Column from 'components/grid/column';
import NavMenu from './components/nav-menu';
import NavAlt from './components/nav-alt';
import SubmenuPanel from './components/submenu-panel';
import NotificationsPanel from './components/notifications-panel';
import defaultConfig from './config';
import { HeaderWrapper } from './styles';
import { addNotifications } from '../../utils/storage';
const isServer = typeof window === 'undefined';
class Header extends PureComponent {
static propTypes = {
/** override deafult styles in app */
className: PropTypes.string,
/** called after search */
setQueryToUrl: PropTypes.func,
/** pass custom js router component */
NavLinkComponent: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
/** handle opening contact us modal */
openContactUsModal: PropTypes.func,
/** override app base url */
appUrl: PropTypes.string,
/** override navigation config */
navMain: PropTypes.array,
/** pass custom languages */
languages: PropTypes.array,
/** override active pathname to set link to active */
pathname: PropTypes.string,
/** override control of submenu and make open menu fill whole screen */
fullScreen: PropTypes.bool,
/** custom action after language selection */
afterLangSelect: PropTypes.func,
/** path to custom logo */
customLogo: PropTypes.string,
/** allows to pass "theme" down to the header */
theme: PropTypes.string,
/** makes the header "slimmer" */
slim: PropTypes.bool,
/** list of notifications to display */
notifications: PropTypes.array,
};
static defaultProps = {
appUrl: APP_URL,
theme: 'default',
...defaultConfig,
};
state = {
pathname: this.props.pathname || '',
showSubmenu: false,
showNotificationsPanel: false,
clickOutside: this.props.fullScreen,
lang: isServer
? 'en'
: JSON.parse(localStorage.getItem('txlive:selectedlang')) || 'en',
loggedIn: false,
loggingIn: false,
};
componentDidMount() {
this.checkLoggedIn();
this.findPathname();
}
checkLoggedIn = () => {
const { theme } = this.props;
const query = (!isServer && qs.parse(window.location.search)) || {};
const urlToken = query && query.token;
const isTokenValid =
moment() < moment(localStorage.getItem('userTokenExpirationDate'));
const token = urlToken || localStorage.getItem('userToken');
if (urlToken && !isServer && isTokenValid) {
delete query.token;
const cleanQuery = query && qs.stringify(query);
window.history.pushState(
{},
'',
`${window.location.pathname}${cleanQuery ? `?${cleanQuery}` : ''}`
);
}
// TODO: Check if pro is authenticated?
if (token && theme !== 'pro') {
this.setState({ loggedIn: false, loggingIn: true });
checkLoggedIn(token)
.then((response) => {
if (response.status < 400 && response.data) {
this.setState({ loggedIn: true, loggingIn: false });
} else {
this.setState({ loggedIn: false, loggingIn: false });
}
})
.catch(() => {
this.setState({ loggedIn: false, loggingIn: false });
});
}
};
findPathname = () => {
if (!this.props.pathname && !isServer) {
this.setState({ pathname: window.location.pathname });
}
};
handleLangSelect = (lang) => {
const { afterLangSelect } = this.props;
if (!isServer && window?.Transifex?.live) {
window.Transifex.live.translateTo(lang);
}
this.setState({ lang });
if (afterLangSelect) {
afterLangSelect(lang);
}
};
render() {
const {
className,
theme,
appUrl,
navMain,
customLogo,
languages,
NavLinkComponent,
fullScreen,
slim,
notifications,
} = this.props;
const {
showSubmenu,
clickOutside,
lang,
showNotificationsPanel,
} = this.state;
const activeLang = languages && languages.find((l) => l.value === lang);
const ids = notifications?.map((item) => item.id) || [];
return (
<MediaContextProvider>
<HeaderWrapper
className={className}
slim={slim}
fullScreen={fullScreen}
theme={theme}
showSubmenu={showSubmenu}
>
<Row className="nav-row">
<Column className="nav-column">
{!fullScreen || (fullScreen && showSubmenu) ? (
<>
<NavLink
className="logo"
href="/"
appUrl={appUrl}
NavLinkComponent={NavLinkComponent}
>
<img
src={customLogo || gfwLogo}
alt="Global Forest Watch"
width="76"
height="76"
/>
</NavLink>
<div className="nav">
<Media greaterThanOrEqual="medium" className="nav-desktop">
<NavMenu
{...this.props}
{...this.state}
menuItems={navMain}
toggleShowSubmenu={() => {
if (!clickOutside) {
this.setState({ showSubmenu: true });
} else if (!clickOutside || fullScreen) {
this.setState({ showSubmenu: false });
} else {
this.setState({ showSubmenu: false });
}
}}
/>
<NavAlt
{...this.props}
{...this.state}
activeLang={activeLang}
handleLangSelect={this.handleLangSelect}
handleShowSubmenu={(show) =>
this.setState({ showSubmenu: show })}
handleShowNotificationsPanel={() => {
if (!clickOutside) {
// add notifications to storage
addNotifications(ids);
this.setState({ showNotificationsPanel: true });
} else if (!clickOutside || fullScreen) {
this.setState({ showNotificationsPanel: false });
} else {
this.setState({ showNotificationsPanel: false });
}
}}
/>
</Media>
<Media lessThan="medium" className="nav-mobile">
<OutsideClickHandler
onOutsideClick={() => {
if (!showSubmenu && !clickOutside) {
this.setState({ showSubmenu: false });
}
}}
>
<div className="nav-item nav-more">
{showSubmenu && (
<button
type="button"
className="nav-link"
onClick={() => {
if (!clickOutside || fullScreen) {
this.setState({ showSubmenu: false });
}
}}
>
close
<CloseIcon className="icon-submenu icon-close" />
</button>
)}
{!showSubmenu && (
<button
type="button"
className="nav-link"
onClick={() => {
if (!clickOutside) {
this.setState({ showSubmenu: true });
}
}}
>
more
<MenuIcon className="icon-submenu icon-menu" />
</button>
)}
</div>
</OutsideClickHandler>
</Media>
</div>
</>
) : (
<button
className="logo"
onClick={() => this.setState({ showSubmenu: true })}
>
<img
src={customLogo || gfwLogo}
alt="Global Forest Watch"
width="76"
height="76"
/>
</button>
)}
</Column>
</Row>
{showNotificationsPanel && (
<OutsideClickHandler
onOutsideClick={() => {
this.setState({
showNotificationsPanel: false,
clickOutside: true,
});
setTimeout(() => this.setState({ clickOutside: false }), 50);
}}
>
<NotificationsPanel
slim={slim}
notifications={notifications}
handleClose={() =>
this.setState({ showNotificationsPanel: false })}
/>
</OutsideClickHandler>
)}
{showSubmenu && (
<OutsideClickHandler
onOutsideClick={() => {
if (!fullScreen) {
this.setState({ showSubmenu: false, clickOutside: true });
setTimeout(() => this.setState({ clickOutside: false }), 50);
}
}}
>
<SubmenuPanel
{...this.props}
{...this.state}
handleLangSelect={this.handleLangSelect}
activeLang={activeLang}
hideMenu={() => this.setState({ showSubmenu: false })}
/>
</OutsideClickHandler>
)}
</HeaderWrapper>
</MediaContextProvider>
);
}
}
export default Header;