blob: 2d151b880630694d4effd10a0569a5929ece1180 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { FC, memo, useState, useEffect } from 'react';
import { Navbar, Nav, Button } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { Link, NavLink, useLocation, useMatch } from 'react-router-dom';
import classnames from 'classnames';
import { userCenter, floppyNavigation, isLight } from '@/utils';
import {
loggedUserInfoStore,
siteInfoStore,
brandingStore,
loginSettingStore,
themeSettingStore,
sideNavStore,
} from '@/stores';
import { logout, useQueryNotificationStatus } from '@/services';
import { Icon, MobileSideNav } from '@/components';
import NavItems from './components/NavItems';
import SearchInput from './components/SearchInput';
import './index.scss';
const Header: FC = () => {
const location = useLocation();
const { user, clear: clearUserStore } = loggedUserInfoStore();
const { t } = useTranslation();
const siteInfo = siteInfoStore((state) => state.siteInfo);
const brandingInfo = brandingStore((state) => state.branding);
const loginSetting = loginSettingStore((state) => state.login);
const { updateReview } = sideNavStore();
const { data: redDot } = useQueryNotificationStatus();
const [showMobileSideNav, setShowMobileSideNav] = useState(false);
const [showMobileSearchInput, setShowMobileSearchInput] = useState(false);
/**
* Automatically append `tag` information when creating a question
*/
const tagMatch = useMatch('/tags/:slugName');
let askUrl = '/questions/add';
if (tagMatch && tagMatch.params.slugName) {
askUrl = `${askUrl}?tags=${encodeURIComponent(tagMatch.params.slugName)}`;
}
useEffect(() => {
updateReview({
can_revision: Boolean(redDot?.can_revision),
revision: Number(redDot?.revision),
});
}, [redDot]);
const handleLogout = async (evt) => {
evt.preventDefault();
await logout();
clearUserStore();
window.location.replace(window.location.href);
};
useEffect(() => {
setShowMobileSearchInput(false);
setShowMobileSideNav(false);
}, [location.pathname]);
let navbarStyle = 'theme-light';
let themeMode = 'light';
const { theme, theme_config } = themeSettingStore((_) => _);
if (theme_config?.[theme]?.navbar_style) {
// const color = theme_config[theme].navbar_style.startsWith('#')
themeMode = isLight(theme_config[theme].navbar_style) ? 'light' : 'dark';
navbarStyle = `theme-${themeMode}`;
}
useEffect(() => {
const handleResize = () => {
if (window.innerWidth >= 1199.9) {
setShowMobileSideNav(false);
setShowMobileSearchInput(false);
}
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return (
<Navbar
data-bs-theme={themeMode}
expand="xl"
className={classnames('sticky-top', navbarStyle)}
style={{
backgroundColor: theme_config[theme].navbar_style,
}}
id="header">
<div className="w-100 d-flex align-items-center px-3">
<Navbar.Toggle
className="answer-navBar me-2"
onClick={() => {
setShowMobileSideNav(!showMobileSideNav);
setShowMobileSearchInput(false);
}}
/>
<Navbar.Brand
to="/"
as={Link}
className="lh-1 me-0 me-sm-5 p-0 nav-text">
{brandingInfo.logo ? (
<>
<img
className="d-none d-xl-block logo me-0"
src={brandingInfo.logo}
alt={siteInfo.name}
/>
<img
className="xl-none logo me-0"
src={brandingInfo.mobile_logo || brandingInfo.logo}
alt={siteInfo.name}
/>
</>
) : (
<span>{siteInfo.name}</span>
)}
</Navbar.Brand>
<SearchInput className="d-none d-lg-block maxw-560" />
<Nav className="d-block d-lg-none me-2 ms-auto">
<Button
variant="link"
onClick={() => {
setShowMobileSideNav(false);
setShowMobileSearchInput(!showMobileSearchInput);
}}
className="p-0 btn-no-border icon-link nav-link d-flex align-items-center justify-content-center">
<Icon name="search" className="lh-1 fs-4" />
</Button>
</Nav>
{/* pc nav */}
{user?.username ? (
<Nav className="d-flex align-items-center flex-nowrap flex-row">
<Nav.Item className="me-2 d-block d-xl-none">
<NavLink
to={askUrl}
className="d-block icon-link nav-link text-center">
<Icon name="plus-lg" className="lh-1 fs-4" />
</NavLink>
</Nav.Item>
<Nav.Item className="me-2 d-none d-xl-block">
<NavLink
to={askUrl}
className="nav-link d-flex align-items-center text-capitalize text-nowrap">
<Icon name="plus-lg" className="me-2 lh-1 fs-4" />
<span>{t('btns.create')}</span>
</NavLink>
</Nav.Item>
<NavItems redDot={redDot} userInfo={user} logOut={handleLogout} />
</Nav>
) : (
<>
<Link
className={classnames('me-2 btn btn-link', {
'link-light': navbarStyle === 'theme-dark',
'link-primary': navbarStyle !== 'theme-dark',
})}
onClick={() => floppyNavigation.storageLoginRedirect()}
to={userCenter.getLoginUrl()}>
{t('btns.login')}
</Link>
{loginSetting.allow_new_registrations && (
<Link
className={classnames(
'btn',
navbarStyle === 'theme-dark' ? 'btn-light' : 'btn-primary',
)}
to={userCenter.getSignUpUrl()}>
{t('btns.signup')}
</Link>
)}
</>
)}
</div>
{showMobileSearchInput && (
<div className="w-100 px-3 mt-2 d-block d-lg-none">
<SearchInput />
</div>
)}
<MobileSideNav show={showMobileSideNav} onHide={setShowMobileSideNav} />
</Navbar>
);
};
export default memo(Header);