Sachin Chaurasiya 9f148031f5
fix(ui): tour page (#12122)
Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com>
2023-06-23 22:03:26 -07:00

458 lines
13 KiB
TypeScript

/*
* Copyright 2022 Collate.
* Licensed 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 { Space, Typography } from 'antd';
import { AxiosError } from 'axios';
import { useGlobalSearchProvider } from 'components/GlobalSearchProvider/GlobalSearchProvider';
import { useTourProvider } from 'components/TourProvider/TourProvider';
import { tabsInfo } from 'constants/explore.constants';
import {
urlGitbookDocs,
urlGithubRepo,
urlJoinSlack,
} from 'constants/URL.constants';
import { CurrentTourPageType } from 'enums/tour.enum';
import { isEmpty, isString, max } from 'lodash';
import { observer } from 'mobx-react';
import Qs from 'qs';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Link, useHistory, useLocation } from 'react-router-dom';
import { toast } from 'react-toastify';
import { getVersion } from 'rest/miscAPI';
import { extractDetailsFromToken } from 'utils/AuthProvider.util';
import { getEntityName } from 'utils/EntityUtils';
import appState from '../../AppState';
import { ReactComponent as IconAPI } from '../../assets/svg/api.svg';
import { ReactComponent as IconDoc } from '../../assets/svg/doc.svg';
import { ReactComponent as IconExternalLink } from '../../assets/svg/external-links.svg';
import { ReactComponent as IconSlackGrey } from '../../assets/svg/slack-grey.svg';
import { ReactComponent as IconVersionBlack } from '../../assets/svg/version-black.svg';
import {
getExplorePath,
getTeamAndUserDetailsPath,
getUserPath,
ROUTES,
TERM_ADMIN,
TERM_USER,
TOUR_SEARCH_TERM,
} from '../../constants/constants';
import {
addToRecentSearched,
getNonDeletedTeams,
} from '../../utils/CommonUtils';
import SVGIcons, { Icons } from '../../utils/SvgUtils';
import { showErrorToast } from '../../utils/ToastUtils';
import { useAuthContext } from '../authentication/auth-provider/AuthProvider';
import NavBar from '../nav-bar/NavBar';
import './app-bar.style.less';
const Appbar: React.FC = (): JSX.Element => {
const location = useLocation();
const history = useHistory();
const { t } = useTranslation();
const { isTourOpen, updateTourPage, updateTourSearch, tourSearchValue } =
useTourProvider();
const {
isAuthDisabled,
isAuthenticated,
isProtectedRoute,
isTourRoute,
onLogoutHandler,
} = useAuthContext();
const { searchCriteria } = useGlobalSearchProvider();
const parsedQueryString = Qs.parse(
location.search.startsWith('?')
? location.search.substr(1)
: location.search
);
const searchQuery = isString(parsedQueryString.search)
? parsedQueryString.search
: '';
const [searchValue, setSearchValue] = useState(searchQuery);
const [isOpen, setIsOpen] = useState<boolean>(false);
const [isFeatureModalOpen, setIsFeatureModalOpen] = useState<boolean>(false);
const [version, setVersion] = useState<string>('');
const handleFeatureModal = (value: boolean) => {
setIsFeatureModalOpen(value);
};
const handleSearchChange = (value: string) => {
setSearchValue(value);
if (isTourOpen) {
updateTourSearch(value);
} else {
value ? setIsOpen(true) : setIsOpen(false);
}
};
const supportLink = [
{
label: (
<Space
className="cursor-pointer w-full"
size={4}
onClick={() => history.push(ROUTES.TOUR)}>
<SVGIcons
alt="tour-con"
className="align-middle m-r-xss"
icon={Icons.TOUR}
width="12"
/>
<span className="text-base-color">{t('label.tour')}</span>
</Space>
),
key: 'tour',
},
{
label: (
<a
className="link-title"
href={urlGitbookDocs}
rel="noreferrer"
target="_blank">
<Space size={4}>
<IconDoc
className="tw-align-middle tw--mt-0.5 tw-mr-0.5"
height={14}
name="Doc icon"
width={14}
/>
<span className="text-base-color">{t('label.doc-plural')}</span>
<IconExternalLink
className="text-base-color m-l-xss"
height={14}
width={14}
/>
</Space>
</a>
),
key: 'docs',
},
{
label: (
<Link className="link-title" to={ROUTES.SWAGGER}>
<Space size={4}>
<IconAPI
className="tw-align-middle tw--mt-0.5 tw-mr-0.5"
height={14}
name="API icon"
width={14}
/>
<span className="text-base-color">{t('label.api-uppercase')}</span>
</Space>
</Link>
),
key: 'api',
},
{
label: (
<a
className="link-title"
href={urlJoinSlack}
rel="noreferrer"
target="_blank">
<Space size={4}>
<IconSlackGrey
className="tw-align-middle tw--mt-0.5 tw-mr-0.5"
height={14}
name="slack icon"
width={14}
/>
<span className="text-base-color">{t('label.slack-support')}</span>
<IconExternalLink
className="text-base-color m-l-xss"
height={14}
width={14}
/>
</Space>
</a>
),
key: 'slack',
},
{
label: (
<Space
className="cursor-pointer w-full"
size={4}
onClick={() => handleFeatureModal(true)}>
<SVGIcons
alt="Doc icon"
className="align-middle m-r-xss"
icon={Icons.WHATS_NEW}
width="14"
/>
<span className="text-base-color">{t('label.whats-new')}</span>
</Space>
),
key: 'whats-new',
},
{
label: (
<a
className="link-title"
href={urlGithubRepo}
rel="noreferrer"
target="_blank">
<Space size={4}>
<IconVersionBlack
className="tw-align-middle tw--mt-0.5 tw-mr-0.5"
height={14}
name="Version icon"
width={14}
/>
<span className="text-base-color hover:text-primary">{`${t(
'label.version'
)} ${(version ? version : '?').split('-')[0]}`}</span>
<IconExternalLink
className="text-base-color m-l-xss"
height={14}
width={14}
/>
</Space>
</a>
),
key: 'versions',
},
];
const getUsersRoles = (userRoleArr: string[], name: string) => {
return (
<div>
<div className="text-grey-muted tw-text-xs">{name}</div>
{userRoleArr.map((userRole, i) => (
<Typography.Paragraph
className="ant-typography-ellipsis-custom font-normal"
ellipsis={{ tooltip: true }}
key={i}>
{userRole}
</Typography.Paragraph>
))}
<hr className="tw-my-1.5" />
</div>
);
};
const getUserName = () => {
const currentUser = isAuthDisabled
? appState.nonSecureUserDetails
: appState.userDetails;
return currentUser?.displayName || currentUser?.name || TERM_USER;
};
const getUserData = () => {
const currentUser = isAuthDisabled
? appState.nonSecureUserDetails
: appState.userDetails;
const name = currentUser?.displayName || currentUser?.name || TERM_USER;
const roles = currentUser?.roles?.map((r) => getEntityName(r)) || [];
const inheritedRoles =
currentUser?.inheritedRoles?.map((r) => getEntityName(r)) || [];
currentUser?.isAdmin && roles.unshift(TERM_ADMIN);
const userTeams = getNonDeletedTeams(currentUser?.teams ?? []);
const teams = userTeams.splice(0, 3);
const remainingTeamsCount = max([userTeams.length, 0]);
return (
<div className="tw-max-w-xs" data-testid="greeting-text">
<Link
data-testid="user-name"
to={getUserPath(currentUser?.name as string)}>
{' '}
<Typography.Paragraph
className="ant-typography-ellipsis-custom font-medium cursor-pointer text-link-color"
ellipsis={{ rows: 1, tooltip: true }}>
{name}
</Typography.Paragraph>
</Link>
<hr className="tw-my-1.5" />
{roles.length > 0 ? getUsersRoles(roles, t('label.role-plural')) : null}
{inheritedRoles.length > 0
? getUsersRoles(inheritedRoles, t('label.inherited-role-plural'))
: null}
{teams.length > 0 ? (
<div>
<span className="text-grey-muted tw-text-xs">
{t('label.team-plural')}
</span>
{teams.map((t, i) => (
<Typography.Paragraph
className="ant-typography-ellipsis-custom text-sm"
ellipsis={{ tooltip: true }}
key={i}>
<Link to={getTeamAndUserDetailsPath(t.name as string)}>
{t.displayName || t.name}
</Link>
</Typography.Paragraph>
))}
{remainingTeamsCount ? (
<Link
className="more-teams-pill"
to={getUserPath(currentUser?.name as string)}>
{remainingTeamsCount} {t('label.more')}
</Link>
) : null}
</div>
) : null}
</div>
);
};
const profileDropdown = [
{
name: getUserData(),
to: '',
disabled: false,
icon: <></>,
isText: true,
},
];
const searchHandler = (value: string) => {
if (!isTourOpen) {
setIsOpen(false);
addToRecentSearched(value);
const defaultTab: string =
searchCriteria !== '' ? tabsInfo[searchCriteria].path : '';
history.push(
getExplorePath({
tab: defaultTab,
search: value,
isPersistFilters: false,
})
);
}
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
const target = e.target as HTMLInputElement;
if (e.key === 'Enter') {
if (isTourOpen && searchValue === TOUR_SEARCH_TERM) {
updateTourPage(CurrentTourPageType.EXPLORE_PAGE);
updateTourSearch('');
}
searchHandler(target.value);
}
};
const handleOnclick = () => {
searchHandler(searchValue);
};
const handleClear = () => {
setSearchValue('');
searchHandler('');
};
const fetchOMVersion = () => {
getVersion()
.then((res) => {
setVersion(res.version);
})
.catch((err: AxiosError) => {
showErrorToast(
err,
t('server.entity-fetch-error', {
entity: t('label.version'),
})
);
});
};
useEffect(() => {
setSearchValue(searchQuery);
}, [searchQuery]);
useEffect(() => {
if (isTourOpen) {
setSearchValue(tourSearchValue);
}
}, [tourSearchValue, isTourOpen]);
useEffect(() => {
if (isAuthDisabled) {
fetchOMVersion();
} else {
if (!isEmpty(appState.userDetails)) {
fetchOMVersion();
}
}
}, [appState.userDetails, isAuthDisabled]);
useEffect(() => {
const handleDocumentVisibilityChange = () => {
if (
isProtectedRoute(location.pathname) &&
isTourRoute(location.pathname)
) {
return;
}
const { isExpired, exp } = extractDetailsFromToken();
if (!document.hidden && isExpired) {
exp && toast.info(t('message.session-expired'));
onLogoutHandler();
}
};
addEventListener('focus', handleDocumentVisibilityChange);
return () => {
removeEventListener('focus', handleDocumentVisibilityChange);
};
}, []);
return (
<>
{isProtectedRoute(location.pathname) &&
(isAuthDisabled || isAuthenticated) ? (
<NavBar
handleClear={handleClear}
handleFeatureModal={handleFeatureModal}
handleKeyDown={handleKeyDown}
handleOnClick={handleOnclick}
handleSearchBoxOpen={setIsOpen}
handleSearchChange={handleSearchChange}
isFeatureModalOpen={isFeatureModalOpen}
isSearchBoxOpen={isOpen}
pathname={location.pathname}
profileDropdown={profileDropdown}
searchValue={searchValue || ''}
supportDropdown={supportLink}
username={getUserName()}
/>
) : null}
</>
);
};
export default observer(Appbar);