at the end of the day, it was inevitable
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import HeaderSettings from './HeaderSettings';
|
||||
import SettingsPopup from './SettingsPopup';
|
||||
import cx from 'classnames';
|
||||
import MainTabsLinks from './MainTabsLinks';
|
||||
import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup';
|
||||
import HeaderLogo from './HeaderLogo';
|
||||
import HeaderDots from './HeaderDots';
|
||||
|
||||
export class AppHeader extends React.Component {
|
||||
static propTypes = {
|
||||
appCommonState: PropTypes.object.isRequired,
|
||||
actions: PropTypes.object.isRequired,
|
||||
userFirstName: PropTypes.string,
|
||||
userLastName: PropTypes.string,
|
||||
userRole: PropTypes.string.isRequired,
|
||||
restrictions: PropTypes.object.isRequired,
|
||||
themeOptions: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
state = {
|
||||
active: false,
|
||||
mobile: false,
|
||||
activeSecondaryMenuMobile: false
|
||||
};
|
||||
|
||||
toggleResponsiveMenu = () => {
|
||||
this.props.actions.toggleSidebar();
|
||||
};
|
||||
|
||||
activeSearchFunc = () => {
|
||||
this.setState({ active: !this.state.active });
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
appCommonState,
|
||||
restrictions,
|
||||
actions,
|
||||
userFirstName,
|
||||
userLastName,
|
||||
themeOptions
|
||||
} = this.props;
|
||||
const mainTabs = Object.keys(appCommonState.tabs);
|
||||
|
||||
const {
|
||||
headerBackgroundColor,
|
||||
enableHeaderShadow,
|
||||
enableMobileMenuSmall
|
||||
} = themeOptions;
|
||||
|
||||
const settingsPopupVisible = appCommonState.isSettingsPopupVisible;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<CSSTransitionGroup
|
||||
component="div"
|
||||
className={cx('app-header', headerBackgroundColor, {
|
||||
'header-shadow': enableHeaderShadow
|
||||
})}
|
||||
transitionName="HeaderAnimation"
|
||||
transitionAppear
|
||||
transitionAppearTimeout={1500}
|
||||
transitionEnter={false}
|
||||
transitionLeave={false}
|
||||
>
|
||||
<HeaderLogo />
|
||||
<div
|
||||
className={cx('app-header__content', {
|
||||
'header-mobile-open': enableMobileMenuSmall
|
||||
})}
|
||||
>
|
||||
<div className="app-header-left" data-tour="app-header-left">
|
||||
<MainTabsLinks
|
||||
tabs={appCommonState.tabs}
|
||||
restrictions={restrictions}
|
||||
actions={actions}
|
||||
/>
|
||||
</div>
|
||||
<div className="app-header-right">
|
||||
<HeaderDots
|
||||
mainTabs={mainTabs}
|
||||
restrictions={restrictions}
|
||||
planDetails={restrictions.plans}
|
||||
/>
|
||||
<HeaderSettings
|
||||
isThereSomethingNew={appCommonState.isThereSomethingNew}
|
||||
langs={appCommonState.langs}
|
||||
userFirstName={userFirstName}
|
||||
userLastName={userLastName}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{settingsPopupVisible && (
|
||||
<SettingsPopup
|
||||
hidePopup={actions.hideUserSettingsPopup}
|
||||
setErrorMsg={actions.setSettingsPopupError}
|
||||
changePassword={actions.changeUserPassword}
|
||||
errorMsg={appCommonState.settingsPopupError}
|
||||
/>
|
||||
)}
|
||||
</CSSTransitionGroup>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AppHeader;
|
||||
@@ -0,0 +1,99 @@
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Fragment } from 'react';
|
||||
import { Slider } from 'react-burgers';
|
||||
import cx from 'classnames';
|
||||
import { faEllipsisV } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Button } from 'reactstrap';
|
||||
import reduxConnect from '../../../redux/utils/connect';
|
||||
import translate from 'react-i18next/dist/commonjs/translate';
|
||||
import { compose } from 'redux';
|
||||
|
||||
class AppMobileMenu extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
active: false,
|
||||
mobile: false,
|
||||
activeSecondaryMenuMobile: false
|
||||
};
|
||||
}
|
||||
|
||||
toggleMobileSidebar = () => {
|
||||
const { setEnableMobileMenu } = this.props.actions;
|
||||
const { enableMobileMenu } = this.props.appState.themeOptions;
|
||||
setEnableMobileMenu(!enableMobileMenu);
|
||||
};
|
||||
|
||||
toggleMobileSmall = () => {
|
||||
const { setEnableMobileMenuSmall } = this.props.actions;
|
||||
const { enableMobileMenuSmall } = this.props.appState.themeOptions;
|
||||
setEnableMobileMenuSmall(!enableMobileMenuSmall);
|
||||
};
|
||||
|
||||
state = {
|
||||
openLeft: false,
|
||||
openRight: false,
|
||||
relativeWidth: false,
|
||||
width: 280,
|
||||
noTouchOpen: false,
|
||||
noTouchClose: false
|
||||
};
|
||||
|
||||
changeActive = () => {
|
||||
this.setState({ active: !this.state.active });
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="app-header__mobile-menu" data-tour="mobile-left-menu">
|
||||
<div onClick={this.toggleMobileSidebar}>
|
||||
<Slider
|
||||
width={26}
|
||||
lineHeight={2}
|
||||
lineSpacing={5}
|
||||
color="#6c757d"
|
||||
active={this.state.active}
|
||||
onClick={this.changeActive}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-header__menu">
|
||||
<span onClick={this.toggleMobileSmall}>
|
||||
<Button
|
||||
size="sm"
|
||||
className={cx('btn-icon btn-icon-only', {
|
||||
active: this.state.activeSecondaryMenuMobile
|
||||
})}
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
this.setState({
|
||||
activeSecondaryMenuMobile: !this.state
|
||||
.activeSecondaryMenuMobile
|
||||
})
|
||||
}
|
||||
>
|
||||
<div className="btn-icon-wrapper">
|
||||
<FontAwesomeIcon icon={faEllipsisV} />
|
||||
</div>
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AppMobileMenu.propTypes = {
|
||||
actions: PropTypes.object,
|
||||
appState: PropTypes.object
|
||||
};
|
||||
|
||||
const applyDecorators = compose(
|
||||
reduxConnect('appState', ['appState']),
|
||||
translate(['tabsContent'], { wait: true })
|
||||
);
|
||||
|
||||
export default applyDecorators(AppMobileMenu);
|
||||
@@ -0,0 +1,168 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { translate } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
UncontrolledDropdown,
|
||||
DropdownToggle,
|
||||
DropdownMenu,
|
||||
Col,
|
||||
Row,
|
||||
Button,
|
||||
DropdownItem
|
||||
} from 'reactstrap';
|
||||
|
||||
import { IoIosGrid } from 'react-icons/io';
|
||||
import Notifications from './Notifications';
|
||||
import LangSettingsMenu from './LangSettingsMenu';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faAngleDown } from '@fortawesome/free-solid-svg-icons';
|
||||
import { planRoutes } from '../Account/Plans/UserPlans';
|
||||
import { convertUTCtoLocal } from '../../../common/helper';
|
||||
|
||||
class HeaderDots extends React.Component {
|
||||
static propTypes = {
|
||||
mainTabs: PropTypes.array.isRequired,
|
||||
restrictions: PropTypes.object.isRequired,
|
||||
planDetails: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
validateTab(tab) {
|
||||
if (tab === 'analyze') {
|
||||
if (!this.props.restrictions) {
|
||||
return false;
|
||||
}
|
||||
const permissions = this.props.restrictions.permissions;
|
||||
return permissions.analytics;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { t, mainTabs, planDetails, restrictions } = this.props;
|
||||
const isFreeAccount = planDetails.price === 0;
|
||||
const isRTL = document.documentElement.dir === 'rtl';
|
||||
|
||||
return (
|
||||
<div className="header-dots">
|
||||
<UncontrolledDropdown nav inNavbar>
|
||||
<DropdownToggle nav>
|
||||
{t('plans.currentPlan')}
|
||||
<FontAwesomeIcon className="ml-2 opacity-5" icon={faAngleDown} />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu
|
||||
className={`dropdown-menu-rounded rm-pointers${
|
||||
isRTL ? ' dropdown-menu-left' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="dropdown-menu-header">
|
||||
<div className="dropdown-menu-header-inner bg-success">
|
||||
<div className="menu-header-image opacity-1"></div>
|
||||
<div className="menu-header-content text-left">
|
||||
<h5 className="menu-header-title font-weight-bold">
|
||||
{isFreeAccount
|
||||
? t('plans.freeBasicAccount')
|
||||
: `$${planDetails.price}`}
|
||||
</h5>
|
||||
{!isFreeAccount && (
|
||||
<p>
|
||||
{restrictions.subStartDate && restrictions.subEndDate
|
||||
? `${convertUTCtoLocal(
|
||||
isRTL
|
||||
? restrictions.subEndDate
|
||||
: restrictions.subStartDate,
|
||||
'MMM D, YYYY'
|
||||
)} - ${convertUTCtoLocal(
|
||||
isRTL
|
||||
? restrictions.subStartDate
|
||||
: restrictions.subEndDate,
|
||||
'MMM D, YYYY'
|
||||
)}`
|
||||
: t('plans.perMonth')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DropdownItem
|
||||
tag={Link}
|
||||
to={`/app/plans/${planRoutes.update}`}
|
||||
className="font-weight-bold"
|
||||
>
|
||||
<i className="dropdown-icon lnr-rocket opacity-8"> </i>
|
||||
{t('plans.upgradePlan')}
|
||||
</DropdownItem>
|
||||
<DropdownItem tag={Link} to={`/app/plans/${planRoutes.txn}`}>
|
||||
<i className="dropdown-icon lnr-list"> </i>
|
||||
{t('plans.yourTransactions')}
|
||||
</DropdownItem>
|
||||
{!isFreeAccount && (
|
||||
<DropdownItem
|
||||
tag={Link}
|
||||
to={`/app/plans/${planRoutes.changeCard}`}
|
||||
>
|
||||
<i className="dropdown-icon lnr-license"> </i>
|
||||
{t('plans.changeCard')}
|
||||
</DropdownItem>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</UncontrolledDropdown>
|
||||
<Button
|
||||
tag={Link}
|
||||
to={`/app/plans/${planRoutes.update}`}
|
||||
size="sm"
|
||||
outline
|
||||
color="success"
|
||||
className="align-self-center mr-3 d-none d-lg-block"
|
||||
>
|
||||
{t('plans.upgradePlan')}
|
||||
</Button>
|
||||
<UncontrolledDropdown className="d-block d-lg-none">
|
||||
<DropdownToggle className="p-0 mr-2" color="link">
|
||||
<div className="icon-wrapper icon-wrapper-alt rounded-circle">
|
||||
<div className="icon-wrapper-bg bg-primary" />
|
||||
<IoIosGrid color="#3f6ad8" fontSize="23px" />
|
||||
</div>
|
||||
</DropdownToggle>
|
||||
<DropdownMenu
|
||||
className={`rm-pointers${isRTL ? ' dropdown-menu-left' : ''}`}
|
||||
>
|
||||
<div className="grid-menu grid-menu-xl grid-menu-3col">
|
||||
{mainTabs.map((tab, i) => {
|
||||
if (!this.validateTab(tab)) return null;
|
||||
return (
|
||||
<Col md="12" key={`main-tab-link-${i}`}>
|
||||
<Button
|
||||
className="btn-icon-vertical btn-square btn-transition"
|
||||
outline
|
||||
color="link"
|
||||
>
|
||||
<Link to={'/app/' + tab} className="nav-link">
|
||||
<Row>
|
||||
<i
|
||||
className={
|
||||
i
|
||||
? 'lnr lnr-exit-up btn-icon-wrapper mr-1'
|
||||
: 'lnr-magnifier btn-icon-wrapper mr-1'
|
||||
}
|
||||
></i>
|
||||
<p>{t('tabs.' + tab)}</p>
|
||||
</Row>
|
||||
</Link>
|
||||
</Button>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</DropdownMenu>
|
||||
</UncontrolledDropdown>
|
||||
<LangSettingsMenu />
|
||||
<Notifications />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(['common'], { wait: true })(HeaderDots);
|
||||
@@ -0,0 +1,74 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
import { Slider } from 'react-burgers'
|
||||
|
||||
import { compose } from 'redux'
|
||||
import reduxConnect from '../../../redux/utils/connect'
|
||||
import translate from 'react-i18next/dist/commonjs/translate'
|
||||
import AppMobileMenu from './AppMobileMenu'
|
||||
|
||||
class HeaderLogo extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
active: false,
|
||||
mobile: false,
|
||||
activeSecondaryMenuMobile: false
|
||||
}
|
||||
}
|
||||
|
||||
toggleEnableClosedSidebar = () => {
|
||||
const { setEnableClosedSidebar } = this.props.actions
|
||||
const { enableClosedSidebar } = this.props.appState.themeOptions
|
||||
setEnableClosedSidebar(!enableClosedSidebar)
|
||||
}
|
||||
|
||||
state = {
|
||||
openLeft: false,
|
||||
openRight: false,
|
||||
relativeWidth: false,
|
||||
width: 280,
|
||||
noTouchOpen: false,
|
||||
noTouchClose: false
|
||||
}
|
||||
|
||||
changeActive = () => {
|
||||
this.setState({ active: !this.state.active })
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="app-header__logo">
|
||||
<div className="logo-src ml-0 ml-lg-2" />
|
||||
<div className="header__pane ml-auto">
|
||||
<div onClick={this.toggleEnableClosedSidebar}>
|
||||
<Slider
|
||||
width={26}
|
||||
lineHeight={2}
|
||||
lineSpacing={5}
|
||||
color="#6c757d"
|
||||
active={this.state.active}
|
||||
onClick={this.changeActive}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AppMobileMenu />
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
HeaderLogo.propTypes = {
|
||||
actions: PropTypes.object,
|
||||
appState: PropTypes.object
|
||||
}
|
||||
|
||||
const applyDecorators = compose(
|
||||
reduxConnect('appState', ['appState']),
|
||||
translate(['tabsContent'], { wait: true })
|
||||
)
|
||||
|
||||
export default applyDecorators(HeaderLogo)
|
||||
@@ -0,0 +1,84 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { translate } from 'react-i18next';
|
||||
import UserSettingsMenu from './UserSettingsMenu';
|
||||
import { DropdownToggle, DropdownMenu, Dropdown } from 'reactstrap';
|
||||
import { faAngleDown, faUser } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
export class HeaderSettings extends React.Component {
|
||||
static propTypes = {
|
||||
userFirstName: PropTypes.string.isRequired,
|
||||
userLastName: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
state = {
|
||||
isOpen: false
|
||||
};
|
||||
|
||||
toggleUserSettingsDrop = () => {
|
||||
this.setState((prev) => ({ isOpen: !prev.isOpen }));
|
||||
};
|
||||
|
||||
render() {
|
||||
const { userFirstName, userLastName } = this.props;
|
||||
const isRTL = document.documentElement.dir === 'rtl';
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="header-btn-lg pr-0">
|
||||
<div className="widget-content p-0">
|
||||
<div className="widget-content-wrapper">
|
||||
<div className="widget-content-left">
|
||||
<Dropdown
|
||||
isOpen={this.state.isOpen}
|
||||
toggle={this.toggleUserSettingsDrop}
|
||||
>
|
||||
<DropdownToggle
|
||||
color="link"
|
||||
title="User Profile"
|
||||
className="d-flex align-items-center p-0"
|
||||
data-tour="app-header-user-settings"
|
||||
>
|
||||
<div className="user-profile">
|
||||
<FontAwesomeIcon
|
||||
className="user-profile-icon"
|
||||
icon={faUser}
|
||||
/>
|
||||
</div>
|
||||
{window.outerWidth >= 768 && (
|
||||
<FontAwesomeIcon
|
||||
className="ml-2 opacity-8"
|
||||
icon={faAngleDown}
|
||||
/>
|
||||
)}
|
||||
</DropdownToggle>
|
||||
<DropdownMenu
|
||||
className={`rm-pointers dropdown-menu-lg${
|
||||
isRTL ? ' dropdown-menu-left' : ''
|
||||
}`}
|
||||
>
|
||||
<UserSettingsMenu
|
||||
toggleMenu={this.toggleUserSettingsDrop}
|
||||
userFirstName={userFirstName}
|
||||
userLastName={userLastName}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<div className="widget-content-left ml-3 header-user-info">
|
||||
<div className="widget-heading">
|
||||
{userFirstName + ' ' + userLastName}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(['common'], { wait: true })(
|
||||
React.memo(HeaderSettings)
|
||||
);
|
||||
@@ -0,0 +1,100 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { compose } from 'redux';
|
||||
import { translate } from 'react-i18next';
|
||||
import i18n from '../../../i18n';
|
||||
import {
|
||||
UncontrolledDropdown,
|
||||
DropdownToggle,
|
||||
DropdownMenu,
|
||||
DropdownItem
|
||||
} from 'reactstrap';
|
||||
import reduxConnect from '../../../redux/utils/connect';
|
||||
|
||||
import Flag from 'react-flagkit';
|
||||
|
||||
const langCountry = {
|
||||
en: 'US',
|
||||
ar: 'SA',
|
||||
fr: 'FR'
|
||||
};
|
||||
|
||||
function LangSettingsMenu(props) {
|
||||
const {
|
||||
t,
|
||||
base: { langs, activeLang },
|
||||
actions,
|
||||
direction = ''
|
||||
} = props;
|
||||
|
||||
const chooseLang = (e) => {
|
||||
const newLang = e.target.dataset.lang;
|
||||
actions.chooseLanguage(newLang);
|
||||
i18n.changeLanguage(newLang);
|
||||
};
|
||||
const isRTL = document.documentElement.dir === 'rtl';
|
||||
|
||||
const dropDownProps = {};
|
||||
if (direction) {
|
||||
dropDownProps.direction = direction;
|
||||
}
|
||||
|
||||
return (
|
||||
<UncontrolledDropdown {...dropDownProps}>
|
||||
<DropdownToggle className="p-0 mr-2" color="link">
|
||||
<div className="icon-wrapper icon-wrapper-alt rounded-circle">
|
||||
<div className="icon-wrapper-bg bg-focus" />
|
||||
<div className="language-icon">
|
||||
<Flag
|
||||
className="mr-3 opacity-8"
|
||||
country={langCountry[activeLang]}
|
||||
size="40"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownToggle>
|
||||
<DropdownMenu
|
||||
className={`rm-pointers${isRTL ? ' dropdown-menu-left' : ''}`}
|
||||
>
|
||||
<div className="dropdown-menu-header">
|
||||
<div className="dropdown-menu-header-inner pt-4 pb-4 bg-focus">
|
||||
<div className="menu-header-content text-center text-white">
|
||||
<h6 className="menu-header-subtitle mt-0">
|
||||
{t('langs.chooseLanguage')}
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{langs.map((lang, i) => {
|
||||
const translateTarget = 'langs.' + lang;
|
||||
return (
|
||||
<DropdownItem
|
||||
key={lang}
|
||||
active={activeLang === lang}
|
||||
data-lang={lang}
|
||||
onClick={chooseLang}
|
||||
>
|
||||
<Flag className="mr-3 opacity-8" country={langCountry[lang]} />
|
||||
{t(translateTarget)}
|
||||
</DropdownItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenu>
|
||||
</UncontrolledDropdown>
|
||||
);
|
||||
}
|
||||
|
||||
LangSettingsMenu.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
base: PropTypes.object.isRequired,
|
||||
actions: PropTypes.object.isRequired,
|
||||
direction: PropTypes.string
|
||||
};
|
||||
|
||||
const applyDecorators = compose(
|
||||
reduxConnect('base', ['common', 'base']),
|
||||
translate(['common'], { wait: true })
|
||||
);
|
||||
|
||||
export default applyDecorators(LangSettingsMenu);
|
||||
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { translate } from 'react-i18next';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
import { Nav, NavItem } from 'reactstrap';
|
||||
import cl from 'classnames';
|
||||
|
||||
export class MainTabsLinks extends React.Component {
|
||||
static propTypes = {
|
||||
tabs: PropTypes.object.isRequired,
|
||||
restrictions: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
actions: PropTypes.object.isRequired,
|
||||
location: PropTypes.object
|
||||
};
|
||||
|
||||
validateTab = (tab) => {
|
||||
if (tab === 'analyze') {
|
||||
if (!this.props.restrictions) {
|
||||
// to prevent: permissions of `undefined`
|
||||
return false;
|
||||
}
|
||||
const permissions = this.props.restrictions.permissions;
|
||||
return permissions.analytics;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
showUpgradeModal = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.actions.toggleUpgradeModal();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, tabs, location } = this.props;
|
||||
|
||||
return (
|
||||
<Nav className="header-megamenu">
|
||||
{Object.keys(tabs).map((tab, i) => {
|
||||
const firstSubTab =
|
||||
tabs[tab].items && tabs[tab].items[0] ? tabs[tab].items[0].url : '';
|
||||
|
||||
if (!this.validateTab(tab)) {
|
||||
return (
|
||||
<NavItem key={tab}>
|
||||
<a
|
||||
href="#"
|
||||
onClick={this.showUpgradeModal}
|
||||
className={cl('nav-link', {
|
||||
active: location.pathname.startsWith(`/app/${tab}`)
|
||||
})}
|
||||
>
|
||||
<i className={`nav-link-icon ${tabs[tab].icon}`}> </i>
|
||||
<p>{t('tabs.' + tab)}</p>
|
||||
</a>
|
||||
</NavItem>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<NavItem key={tab}>
|
||||
<Link
|
||||
to={`/app/${tab}/${firstSubTab}`}
|
||||
className={cl('nav-link', {
|
||||
active: location.pathname.startsWith(`/app/${tab}`)
|
||||
})}
|
||||
>
|
||||
<i className={`nav-link-icon ${tabs[tab].icon}`}> </i>
|
||||
<p>{t('tabs.' + tab)}</p>
|
||||
</Link>
|
||||
</NavItem>
|
||||
);
|
||||
})}
|
||||
</Nav>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(['common'], { wait: true })(
|
||||
withRouter(React.memo(MainTabsLinks))
|
||||
);
|
||||
@@ -0,0 +1,160 @@
|
||||
import React, { Fragment, useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import city3 from '../../../styles/utils/images/dropdown-header/city3.jpg';
|
||||
import PerfectScrollbar from 'react-perfect-scrollbar';
|
||||
import reduxConnect from '../../../redux/utils/connect';
|
||||
import cl from 'classnames';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownToggle,
|
||||
Nav,
|
||||
NavItem,
|
||||
UncontrolledDropdown
|
||||
} from 'reactstrap';
|
||||
import { Interpolate, translate } from 'react-i18next';
|
||||
import { compose } from 'redux';
|
||||
import { IoIosNotificationsOutline } from 'react-icons/io';
|
||||
|
||||
function Notifications({ alerts, t, actions }) {
|
||||
const [alertsList, setAlertsList] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
// Empty list when mounts
|
||||
actions.removeAllAlerts();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const newAlerts = alerts
|
||||
.reverse()
|
||||
.map((alert) => {
|
||||
return typeof alert === 'string' ? { message: alert } : alert;
|
||||
})
|
||||
.map((alert) => {
|
||||
const interpolateParameters = alert ? alert.parameters : {};
|
||||
const i18nKey = alert && `alerts.${alert.type}.${alert.transKey}`;
|
||||
let type, msg;
|
||||
|
||||
type = alert.type ? oldValueMapping[alert.type] : 'warning';
|
||||
|
||||
msg = t(i18nKey, {
|
||||
...interpolateParameters,
|
||||
defaultValue: alert.message || t('error.unknown')
|
||||
});
|
||||
|
||||
return { type, msg };
|
||||
});
|
||||
|
||||
setAlertsList(newAlerts);
|
||||
}, [alerts.length]);
|
||||
|
||||
const isRTL = document.documentElement.dir === 'rtl';
|
||||
|
||||
return (
|
||||
<UncontrolledDropdown>
|
||||
<DropdownToggle className="p-0 mr-2" color="link">
|
||||
<div className="icon-wrapper icon-wrapper-alt rounded-circle">
|
||||
<div className="icon-wrapper-bg bg-danger" />
|
||||
<IoIosNotificationsOutline color="#d92550" fontSize="23px" />
|
||||
<div className="badge badge-dot badge-dot-sm badge-danger">
|
||||
{alertsList.length > 0 ? t('userSettings.notifications') : ''}
|
||||
</div>
|
||||
</div>
|
||||
</DropdownToggle>
|
||||
<DropdownMenu
|
||||
className={cl('dropdown-menu-xl rm-pointers', {
|
||||
'py-0': alertsList.length < 1,
|
||||
'dropdown-menu-left': isRTL
|
||||
})}
|
||||
>
|
||||
<div className="dropdown-menu-header mb-0">
|
||||
<div className="dropdown-menu-header-inner bg-deep-blue">
|
||||
<div
|
||||
className="menu-header-image opacity-1"
|
||||
style={{
|
||||
backgroundImage: 'url(' + city3 + ')'
|
||||
}}
|
||||
/>
|
||||
<div className="menu-header-content text-dark">
|
||||
<h5 className="menu-header-title">
|
||||
{t('userSettings.notifications')}
|
||||
</h5>
|
||||
<h6 className="menu-header-subtitle">
|
||||
<Interpolate
|
||||
i18nKey={
|
||||
alertsList.length > 1
|
||||
? 'userSettings.notificationsSub_plural'
|
||||
: 'userSettings.notificationsSub'
|
||||
}
|
||||
alertLength={alertsList.length}
|
||||
/>
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{alertsList.length > 0 && (
|
||||
<Fragment>
|
||||
<div className="scroll-area-md">
|
||||
<PerfectScrollbar>
|
||||
<div className="p-2">
|
||||
{alertsList.map((item, i) => (
|
||||
<Alert
|
||||
key={i}
|
||||
className="mb-2"
|
||||
style={{ wordBreak: 'break-word' }}
|
||||
color={colorsMapping[item.type]}
|
||||
>
|
||||
<p className="font-size-xs font-weight-bold text-uppercase">
|
||||
{item.type}
|
||||
</p>
|
||||
{item.msg}
|
||||
</Alert>
|
||||
))}
|
||||
</div>
|
||||
</PerfectScrollbar>
|
||||
</div>
|
||||
<Nav vertical>
|
||||
<NavItem className="nav-item-divider" />
|
||||
<NavItem className="nav-item-btn text-center">
|
||||
<Button
|
||||
size="sm"
|
||||
className="btn-shadow btn-wide btn-pill"
|
||||
color="focus"
|
||||
onClick={actions.removeAllAlerts}
|
||||
>
|
||||
{t('userSettings.clearAll')}
|
||||
</Button>
|
||||
</NavItem>
|
||||
</Nav>
|
||||
</Fragment>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</UncontrolledDropdown>
|
||||
);
|
||||
}
|
||||
|
||||
const oldValueMapping = {
|
||||
notice: 'success',
|
||||
warning: 'warning',
|
||||
error: 'error'
|
||||
};
|
||||
|
||||
const colorsMapping = {
|
||||
success: 'success',
|
||||
warning: 'warning',
|
||||
error: 'danger'
|
||||
};
|
||||
|
||||
Notifications.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
alerts: PropTypes.array.isRequired,
|
||||
actions: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const applyDecorators = compose(
|
||||
reduxConnect('alerts', ['common', 'alerts']),
|
||||
translate(['common'], { wait: true })
|
||||
);
|
||||
|
||||
export default applyDecorators(Notifications);
|
||||
@@ -0,0 +1,39 @@
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
import cx from 'classnames'
|
||||
|
||||
class SearchBox extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
activeSearch: false
|
||||
}
|
||||
}
|
||||
|
||||
activeSearchFunc = () => {
|
||||
this.setState({ activeSearch: !this.state.activeSearch })
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={cx('search-wrapper', {
|
||||
active: this.state.activeSearch
|
||||
})}>
|
||||
<div className="input-holder">
|
||||
<input type="text" className="search-input" placeholder="Type to search" />
|
||||
<button onClick={this.activeSearchFunc}
|
||||
className="search-icon">
|
||||
<span />
|
||||
</button>
|
||||
</div>
|
||||
<button onClick={this.activeSearchFunc}
|
||||
className="close" />
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default SearchBox
|
||||
@@ -0,0 +1,122 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { translate } from 'react-i18next';
|
||||
import {
|
||||
Button,
|
||||
Label,
|
||||
Input,
|
||||
FormGroup,
|
||||
Modal,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
ModalFooter
|
||||
} from 'reactstrap';
|
||||
|
||||
export class SettingsPopup extends React.Component {
|
||||
static propTypes = {
|
||||
hidePopup: PropTypes.func.isRequired,
|
||||
setErrorMsg: PropTypes.func.isRequired,
|
||||
changePassword: PropTypes.func.isRequired,
|
||||
errorMsg: PropTypes.string,
|
||||
t: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
};
|
||||
}
|
||||
|
||||
hidePopup = () => {
|
||||
this.props.hidePopup();
|
||||
this.props.setErrorMsg(null);
|
||||
};
|
||||
|
||||
onSubmit = () => {
|
||||
const { t } = this.props;
|
||||
const { oldPassword, newPassword, confirmPassword } = this.state;
|
||||
|
||||
// need more validations
|
||||
if (!oldPassword || !newPassword || !confirmPassword) {
|
||||
return this.props.setErrorMsg(t('userSettings.enterRequiredFields'));
|
||||
}
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
return this.props.setErrorMsg(t('userSettings.passwordsNotMatched'));
|
||||
}
|
||||
|
||||
if (oldPassword && newPassword) {
|
||||
this.props.changePassword(newPassword, oldPassword);
|
||||
}
|
||||
};
|
||||
|
||||
handleChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
this.setState({ [name]: value });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, errorMsg } = this.props;
|
||||
|
||||
return (
|
||||
<Modal isOpen toggle={this.hidePopup} backdrop="static">
|
||||
<ModalHeader toggle={this.hidePopup}>
|
||||
{t('userSettings.changePassword')}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<FormGroup>
|
||||
<Label>
|
||||
{t('userSettings.enterOldPassword')}
|
||||
<span className="text-danger">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
type="password"
|
||||
name="oldPassword"
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<Label>
|
||||
{t('userSettings.enterNewPassword')}
|
||||
<span className="text-danger">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
type="password"
|
||||
name="newPassword"
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<Label>
|
||||
{t('userSettings.retypeNewPassword')}
|
||||
<span className="text-danger">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
type="password"
|
||||
name="confirmPassword"
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<p className="text-danger">{errorMsg}</p>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button color="light" onClick={this.hidePopup}>
|
||||
{t('common:commonWords.Cancel')}
|
||||
</Button>
|
||||
<Button color="primary" onClick={this.onSubmit}>
|
||||
{t('userSettings.changePassword')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(['tabsContent', 'common'], { wait: true })(
|
||||
SettingsPopup
|
||||
);
|
||||
@@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { translate } from 'react-i18next';
|
||||
|
||||
class SubTabWrapper extends React.Component {
|
||||
static propTypes = {
|
||||
activeTabName: PropTypes.string.isRequired,
|
||||
subTabs: PropTypes.array.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
children: PropTypes.object
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, activeTabName, subTabs, children } = this.props;
|
||||
|
||||
return (
|
||||
<div className="rc-tabs-top position-relative" key="sub-tab-wrapper">
|
||||
<div role="tablist" className="rc-tabs-bar" tabIndex="0">
|
||||
<div className="rc-tabs-nav-container">
|
||||
<div className="rc-tabs-nav-wrap mask-line pt-0">
|
||||
<div className="rc-tabs-nav-scroll">
|
||||
<div className="rc-tabs-nav rc-tabs-nav-animated">
|
||||
{subTabs &&
|
||||
subTabs.map((subTab) => {
|
||||
const tabText =
|
||||
activeTabName === 'dashboard'
|
||||
? subTab.title
|
||||
: t('tabs.' + subTab.title);
|
||||
const fullUrl =
|
||||
'/app/' + activeTabName + '/' + subTab.url;
|
||||
|
||||
return (
|
||||
<NavLink
|
||||
to={fullUrl}
|
||||
key={subTab.url}
|
||||
activeClassName="rc-tabs-tab-active rc-tabs-ink-bar rc-tabs-ink-bar-animated"
|
||||
className="rc-tabs-tab"
|
||||
>
|
||||
{tabText}
|
||||
</NavLink>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(['common'], { wait: true })(SubTabWrapper);
|
||||
@@ -0,0 +1,165 @@
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { compose } from 'redux';
|
||||
import { translate } from 'react-i18next';
|
||||
import { Nav, Button, NavItem, NavLink } from 'reactstrap';
|
||||
import PerfectScrollbar from 'react-perfect-scrollbar';
|
||||
import city from '../../../images/city3.jpg';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faUser } from '@fortawesome/free-solid-svg-icons';
|
||||
import { reduxActions } from '../../../redux/utils/connect';
|
||||
import tourPages from './WebTourSteps';
|
||||
|
||||
import { useHistory } from 'react-router';
|
||||
import { planRoutes } from '../Account/Plans/UserPlans';
|
||||
|
||||
function UserSettingsMenu(props) {
|
||||
const { push } = useHistory();
|
||||
|
||||
function hideMenu() {
|
||||
props.toggleMenu();
|
||||
props.actions.setEnableMobileMenuSmall(false);
|
||||
}
|
||||
|
||||
function showUserSettings() {
|
||||
hideMenu();
|
||||
props.actions.showUserSettingsPopup();
|
||||
}
|
||||
|
||||
function onLogout() {
|
||||
hideMenu();
|
||||
props.actions.logout();
|
||||
}
|
||||
|
||||
function tourGuide(path) {
|
||||
const win = window.open(`${path}?webtour=true`, '_blank');
|
||||
win.focus();
|
||||
|
||||
// props.actions.toggleWebTour(); for dev
|
||||
}
|
||||
|
||||
function gotToActivePlan() {
|
||||
hideMenu();
|
||||
push(`/app/plans/${planRoutes.current}`);
|
||||
}
|
||||
|
||||
const { t } = props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="dropdown-menu-header">
|
||||
<div className="dropdown-menu-header-inner bg-info">
|
||||
<div
|
||||
className="menu-header-image opacity-2"
|
||||
style={{
|
||||
backgroundImage: 'url(' + city + ')'
|
||||
}}
|
||||
/>
|
||||
<div className="menu-header-content text-left">
|
||||
<div className="widget-content p-0">
|
||||
<div className="widget-content-wrapper">
|
||||
<div className="widget-content-left mr-3">
|
||||
<div className="user-profile">
|
||||
<FontAwesomeIcon
|
||||
className="user-profile-icon"
|
||||
icon={faUser}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="widget-content-left">
|
||||
<div className="widget-heading">
|
||||
{props.userFirstName + ' ' + props.userLastName}{' '}
|
||||
</div>
|
||||
</div>
|
||||
<div className="widget-content-right ml-auto mr-2">
|
||||
<Button
|
||||
className="btn-pill btn-shadow btn-shine"
|
||||
color="focus"
|
||||
onClick={onLogout}
|
||||
>
|
||||
{t('userSettings.signOut')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div className="scroll-area-xs"> */}
|
||||
<div>
|
||||
<PerfectScrollbar>
|
||||
<Nav vertical>
|
||||
<NavItem>
|
||||
<NavLink
|
||||
tag={Button}
|
||||
type="button"
|
||||
color="link"
|
||||
className="font-size-md w-100"
|
||||
onClick={gotToActivePlan}
|
||||
>
|
||||
{t('plans.activePlanDetails')}
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink
|
||||
tag={Button}
|
||||
type="button"
|
||||
color="link"
|
||||
className="font-size-md w-100"
|
||||
onClick={showUserSettings}
|
||||
>
|
||||
{t('userSettings.changePassword')}
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink
|
||||
className="font-size-md w-100"
|
||||
href="https://www.socialhose.io/en/user-guide"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{t('userSettings.userGuide')}
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem style={{ textAlign: 'start' }}>
|
||||
<div className="mt-2 mb-3 mx-3 px-1">
|
||||
<p className="text-muted font-size-md mb-2">{t('userSettings.guidedTourTooltip')}</p>
|
||||
<div className="d-flex flex-row flex-wrap pl-3">
|
||||
{tourPages.map((tour) => (
|
||||
<Button
|
||||
key={tour.name}
|
||||
className="btn-icon-vertical btn-transition btn-transition-alt pt-2 pb-2 mr-2"
|
||||
outline
|
||||
color="primary"
|
||||
onClick={() => tourGuide(tour.to)}
|
||||
>
|
||||
<i className={`${tour.icon} btn-icon-wrapper mb-2`} />
|
||||
{t(`userSettings.${tour.translateKey}`)}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</NavItem>
|
||||
</Nav>
|
||||
</PerfectScrollbar>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
UserSettingsMenu.propTypes = {
|
||||
toggleMenu: PropTypes.func.isRequired,
|
||||
userFirstName: PropTypes.string.isRequired,
|
||||
userLastName: PropTypes.string.isRequired,
|
||||
actions: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const applyDecorators = compose(
|
||||
reduxActions(),
|
||||
translate(['common'], { wait: true })
|
||||
);
|
||||
|
||||
export default React.memo(applyDecorators(UserSettingsMenu));
|
||||
@@ -0,0 +1,120 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { find } from 'lodash';
|
||||
import { disableBodyScroll, enableBodyScroll } from 'body-scroll-lock';
|
||||
import Tour from 'reactour';
|
||||
import { useHistory, useLocation } from 'react-router';
|
||||
|
||||
import reduxConnect from '../../../redux/utils/connect';
|
||||
import tourPages from './WebTourSteps';
|
||||
|
||||
function WebTour({
|
||||
actions,
|
||||
store: {
|
||||
common: { base },
|
||||
appState: { themeOptions }
|
||||
}
|
||||
}) {
|
||||
const [hasSidebar, setHasSidebar] = useState(window.innerWidth > 991);
|
||||
const [tourData, setTourData] = useState({ content: [] });
|
||||
const location = useLocation();
|
||||
const { replace } = useHistory();
|
||||
|
||||
const { isTourOpen = false } = base;
|
||||
const params = new URLSearchParams(location.search);
|
||||
const webtour = params.get('webtour');
|
||||
|
||||
useEffect(() => {
|
||||
if (webtour) {
|
||||
const tour = find(tourPages, {
|
||||
to: location.pathname
|
||||
});
|
||||
|
||||
if (tour) {
|
||||
setTourData(tour);
|
||||
window.gtag && window.gtag('event', 'tutorial_begin', {
|
||||
name: tour.name
|
||||
});
|
||||
actions.toggleWebTour(); // open tour if param is available
|
||||
}
|
||||
} else {
|
||||
actions.toggleWebTour(); // close if param is removed
|
||||
}
|
||||
}, [webtour]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isTourOpen) {
|
||||
if (window.innerWidth > 991) {
|
||||
!hasSidebar && setHasSidebar(true);
|
||||
} else {
|
||||
hasSidebar && setHasSidebar(false);
|
||||
}
|
||||
}
|
||||
}, [window.innerWidth]);
|
||||
|
||||
const accentColor = '#0094bd';
|
||||
|
||||
function closeWebTour() {
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
|
||||
if (queryParams.has('webtour')) {
|
||||
queryParams.delete('webtour');
|
||||
replace({
|
||||
search: queryParams.toString()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentStep(step) {
|
||||
const stepState = tourData.content;
|
||||
const stepDetails = stepState.find((v, i) => i === step);
|
||||
|
||||
if (step === stepState.length - 1) {
|
||||
window.gtag && window.gtag('event', 'tutorial_complete', {
|
||||
name: tourData.name
|
||||
});
|
||||
}
|
||||
|
||||
if (!hasSidebar) {
|
||||
if (stepDetails.needSidebar) {
|
||||
!themeOptions.enableMobileMenu && actions.setEnableMobileMenu(true);
|
||||
} else {
|
||||
themeOptions.enableMobileMenu && actions.setEnableMobileMenu(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function disableBody(target) {
|
||||
disableBodyScroll(target);
|
||||
}
|
||||
|
||||
function enableBody(target) {
|
||||
enableBodyScroll(target);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tour
|
||||
onRequestClose={closeWebTour}
|
||||
steps={tourData.content}
|
||||
getCurrentStep={getCurrentStep}
|
||||
isOpen={isTourOpen && tourData.content && tourData.content.length > 0}
|
||||
maskClassName="mask"
|
||||
className="helper"
|
||||
rounded={5}
|
||||
startAt={0}
|
||||
closeWithMask={false}
|
||||
accentColor={accentColor}
|
||||
onAfterOpen={disableBody}
|
||||
onBeforeClose={enableBody}
|
||||
disableFocusLock
|
||||
lastStepNextButton={<div className="btn btn-primary">Finish</div>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
WebTour.propTypes = {
|
||||
actions: PropTypes.object,
|
||||
store: PropTypes.object
|
||||
};
|
||||
|
||||
export default reduxConnect()(WebTour);
|
||||
@@ -0,0 +1,172 @@
|
||||
import React from 'react';
|
||||
import i18n from '../../../i18n';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
const baseKey = 'tabsContent:webtour';
|
||||
|
||||
const steps = [
|
||||
{
|
||||
selector: '',
|
||||
content: i18n.t(`${baseKey}.search.start`)
|
||||
},
|
||||
{
|
||||
selector: '[data-tour="left-panel"]',
|
||||
content: i18n.t(`${baseKey}.search.feedsView`),
|
||||
resizeObservables: ['[data-tour="left-panel"]'],
|
||||
needSidebar: true,
|
||||
stepInteraction: false
|
||||
},
|
||||
{
|
||||
selector: '[data-tour="app-header-left"]',
|
||||
content: () => (
|
||||
<Trans i18nKey={`${baseKey}.search.mainTabs`}>
|
||||
There are 3 main pages: <strong>Search</strong> to find content,
|
||||
<strong>Analyze</strong> to generate reports, and <strong>Share</strong>
|
||||
to distribute findings via alerts or webfeeds.
|
||||
</Trans>
|
||||
),
|
||||
onlyWeb: true,
|
||||
stepInteraction: false
|
||||
},
|
||||
{
|
||||
selector: '[data-tour="app-header-user-settings"]',
|
||||
content: i18n.t(`${baseKey}.search.userSettings`),
|
||||
stepInteraction: false
|
||||
},
|
||||
{
|
||||
selector: '[data-tour="search-licenses"]',
|
||||
content: i18n.t(`${baseKey}.search.license`),
|
||||
stepInteraction: false
|
||||
},
|
||||
{
|
||||
selector: '[data-tour="input-field-search"]',
|
||||
content: () => (
|
||||
<p>
|
||||
<Trans i18nKey={`${baseKey}.search.searchField`}>
|
||||
A simple boolean search looks like this:
|
||||
<strong>BMW AND Texas</strong>. Which will find all mentions of “bmw”
|
||||
and "texas”.
|
||||
</Trans>
|
||||
</p>
|
||||
)
|
||||
},
|
||||
{
|
||||
selector: '[data-tour="select-date-range"]',
|
||||
content: i18n.t(`${baseKey}.search.dateRange`),
|
||||
stepInteraction: false
|
||||
},
|
||||
{
|
||||
selector: '[data-tour="select-media-types"]',
|
||||
content: i18n.t(`${baseKey}.search.mediaChannels`)
|
||||
},
|
||||
{
|
||||
selector: '[data-tour="advanced-search"]',
|
||||
content: () => (
|
||||
<Trans i18nKey={`${baseKey}.search.advancedSearch`}>
|
||||
Click on <strong>Advanced Search</strong> to uncover the different
|
||||
options for your search.
|
||||
</Trans>
|
||||
),
|
||||
resizeObservables: ['[data-tour="advanced-search"]']
|
||||
},
|
||||
{
|
||||
selector: '[data-tour="advanced-search"]',
|
||||
content: () => (
|
||||
<Trans i18nKey={`${baseKey}.search.emphasis`}>
|
||||
<strong>Emphasis:</strong> Include or exclude specific words or phrases
|
||||
in the headline of a news article or a blog post.
|
||||
</Trans>
|
||||
),
|
||||
resizeObservables: ['[data-tour="advanced-search-content"]']
|
||||
},
|
||||
{
|
||||
selector: '[data-tour="advanced-search"]',
|
||||
content: () => (
|
||||
<Trans i18nKey={`${baseKey}.search.languages`}>
|
||||
<strong>Languages:</strong> Capture the content that is tagged with the
|
||||
following language(s).
|
||||
</Trans>
|
||||
),
|
||||
resizeObservables: ['[data-tour="advanced-search-content"]']
|
||||
},
|
||||
{
|
||||
selector: '[data-tour="advanced-search"]',
|
||||
content: () => (
|
||||
<Trans i18nKey={`${baseKey}.search.locations`}>
|
||||
<strong>Locations:</strong> Include or exclude content that is geotagged
|
||||
with the following countries or US States.
|
||||
</Trans>
|
||||
),
|
||||
resizeObservables: ['[data-tour="advanced-search-content"]']
|
||||
},
|
||||
{
|
||||
selector: '[data-tour="advanced-search"]',
|
||||
content: () => (
|
||||
<Trans i18nKey={`${baseKey}.search.extras`}>
|
||||
<strong>Extras:</strong> Only show posts with images.
|
||||
</Trans>
|
||||
),
|
||||
resizeObservables: ['[data-tour="advanced-search-content"]']
|
||||
},
|
||||
/* {
|
||||
selector: '[data-tour="search-button"]',
|
||||
content: () => (
|
||||
<Fragment>
|
||||
Click <strong>Search icon</strong>.
|
||||
</Fragment>
|
||||
)
|
||||
}, */
|
||||
{
|
||||
selector: '[data-tour="search-buttons"]',
|
||||
content: i18n.t(`${baseKey}.search.saveSearch`),
|
||||
stepInteraction: false
|
||||
}
|
||||
];
|
||||
|
||||
const analyticsSteps = [
|
||||
{
|
||||
selector: '',
|
||||
content: i18n.t(`${baseKey}.analytics.start`)
|
||||
},
|
||||
{
|
||||
selector: '[data-tour="left-panel"]',
|
||||
content: i18n.t(`${baseKey}.analytics.dragFeed`),
|
||||
resizeObservables: ['[data-tour="left-panel"]'],
|
||||
needSidebar: true
|
||||
},
|
||||
{
|
||||
selector: '[data-tour="drop-feeds-box"]',
|
||||
highlightedSelectors: ['[data-tour="left-panel"]'],
|
||||
content: i18n.t(`${baseKey}.analytics.drop`)
|
||||
},
|
||||
{
|
||||
selector: '[data-tour="analytics-data-range"]',
|
||||
content: i18n.t(`${baseKey}.analytics.dateRange`),
|
||||
observe: '.DateRangePickerInput'
|
||||
},
|
||||
{
|
||||
selector: '[data-tour="create-analytics-button"]',
|
||||
content: i18n.t(`${baseKey}.analytics.create`)
|
||||
}
|
||||
];
|
||||
|
||||
const tourPages = [
|
||||
{
|
||||
translateKey: 'HowToSearch',
|
||||
name: 'How to Search',
|
||||
icon: 'pe-7s-search',
|
||||
to: '/app/search/search',
|
||||
showOn: '/app/search/search',
|
||||
content: steps
|
||||
},
|
||||
{
|
||||
translateKey: 'HowToAnalyze',
|
||||
name: 'How to Analyze',
|
||||
icon: 'pe-7s-graph1',
|
||||
to: '/app/analyze/create',
|
||||
showOn: '/app/analyze',
|
||||
content: analyticsSteps
|
||||
}
|
||||
];
|
||||
|
||||
export default tourPages;
|
||||
Reference in New Issue
Block a user