at the end of the day, it was inevitable

This commit is contained in:
Mo Elzubeir
2022-12-09 08:36:26 -06:00
commit 1218570914
1768 changed files with 887087 additions and 0 deletions
@@ -0,0 +1,216 @@
import React from 'react';
import PropTypes from 'prop-types';
import { translate } from 'react-i18next';
import FilterItem from './FilterItem';
import { ADV_FILTERS_LIMIT } from '../../../redux/modules/appState/search';
import classnames from 'classnames';
import { Button } from 'reactstrap';
export class FilterGroup extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
groupName: PropTypes.string.isRequired,
filters: PropTypes.array.isRequired,
selectedFilters: PropTypes.object,
count: PropTypes.number.isRequired,
totalCount: PropTypes.number.isRequired,
clearPending: PropTypes.bool.isRequired,
callbacks: PropTypes.object.isRequired
};
static translatableGroups = {
articleDate: 'articleDate',
country: 'country',
language: 'language',
articleLanguage: 'language',
sourceCountry: 'country',
sentiment: 'sentiment'
};
translateItem = (itemName) => {
return this.props.t(`${this.translateKey}.${itemName}`, {
defaultValue: itemName
});
};
constructor(props) {
super(props);
this.state = { isExpanded: !!this.props.totalCount };
if (this.props.groupName in FilterGroup.translatableGroups) {
this.translateKey = FilterGroup.translatableGroups[this.props.groupName];
} else {
this.translateItem = (x) => x; //equiv function
}
}
componentDidUpdate = (prevProps) => {
const needUpdate = prevProps.totalCount !== this.props.totalCount;
if (needUpdate) {
this.setState({
isExpanded: !!this.props.totalCount
});
}
};
toggleExpand = () => {
this.setState({
isExpanded: !this.state.isExpanded
});
};
onMoreClick = () => {
this.props.callbacks.moreFilters(this.props.groupName);
};
onLessClick = () => {
this.props.callbacks.lessFilters(this.props.groupName);
};
onRefineClick = () => {
this.props.callbacks.refine();
};
onClearClick = () => {
this.props.callbacks.clearFilters(this.props.groupName);
};
onItemClick = (filterValue) => {
this.props.callbacks.selectFilter(this.props.groupName, filterValue);
};
forEachItem = (fn) => {
return this.props.filters && this.props.filters.slice(0, this.props.count).map(fn);
};
render() {
const clsName = 'filters-table__group';
const { t, selectedFilters, clearPending, count, totalCount } = this.props;
let includedCounter = 0;
let excludedCounter = 0;
for (let value in selectedFilters) {
if (selectedFilters.hasOwnProperty(value)) {
if (selectedFilters[value] === -1) excludedCounter++;
if (selectedFilters[value] === 1) includedCounter++;
}
}
const moreVisible = count < totalCount;
const lessVisible = count > ADV_FILTERS_LIMIT;
const hasSelected = !!excludedCounter || !!includedCounter;
const moreVisibleClass = classnames('filters-table__more', {
'filters-table__more--visible': moreVisible,
'filters-table__more--hidden': !moreVisible && lessVisible,
'filters-table__more--none': !moreVisible && !lessVisible
});
const lessVisibleClass = classnames('filters-table__less', {
'filters-table__less--visible': lessVisible,
'filters-table__less--hidden': !lessVisible && moreVisible,
'filters-table__less--none': !lessVisible && !moreVisible
});
const refineVisibleClass = classnames(
'filters-table__more my-2 ml-2 mr-1',
{
'filters-table__more--none': !hasSelected && !clearPending
}
);
const clearVisibleClass = classnames('filters-table__less my-2 mr-2 ml-1', {
'filters-table__less--none': !hasSelected
});
const isRTL = document.documentElement.dir === 'rtl';
return (
<div
className={
clsName + (this.state.isExpanded ? ' ' + clsName + '--expanded' : '')
}
>
<div className="filters-table__head" onClick={this.toggleExpand}>
{isRTL ? (
<i className="fa fa-chevron-left" />
) : (
<i className="fa fa-chevron-right" />
)}
<i className="fa fa-chevron-down" />
{t('advancedFilters.' + this.props.groupName, {
defaultValue: this.props.groupName
})}
<div>
{!includedCounter && !excludedCounter && (
<i style={{ marginRight: '5px' }} className="lnr lnr-cross" />
)}
{!!includedCounter && (
<span className="included"> {includedCounter}</span>
)}
{!!excludedCounter && (
<span className="excluded">-{excludedCounter}</span>
)}
</div>
</div>
{this.state.isExpanded && (
<div className="filters-table__body">
{this.forEachItem((item) => {
return (
<FilterItem
value={item.value}
title={this.translateItem(item.value)}
key={item.value}
count={item.count}
selectionState={selectedFilters[item.value]}
onItemClick={this.onItemClick}
/>
);
})}
{clearPending && (
<div className="filters-table__clear-msg">
{t('filtersTable.clearMessage')}
</div>
)}
<div className="d-flex justify-content-between">
<Button
className={moreVisibleClass}
color="link"
onClick={this.onMoreClick}
>
<i className="fa fa-angle-double-down" />{' '}
{t('filtersTable.more')}
</Button>
<Button
className={lessVisibleClass}
color="link"
onClick={this.onLessClick}
>
<i className="fa fa-angle-double-up" /> {t('filtersTable.less')}
</Button>
</div>
<div className="d-flex justify-content-between">
<Button
className={refineVisibleClass}
color="primary"
onClick={this.onRefineClick}
>
{t('filtersTable.refine')}
</Button>
<Button className={clearVisibleClass} onClick={this.onClearClick}>
{t('filtersTable.clear')}
</Button>
</div>
</div>
)}
</div>
);
}
}
export default translate(['common'], { wait: true })(FilterGroup);
@@ -0,0 +1,40 @@
import React from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'
import classnames from 'classnames'
export class FilterItem extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
count: PropTypes.number.isRequired,
onItemClick: PropTypes.func.isRequired,
selectionState: PropTypes.number
};
onItemClick = () => {
const { onItemClick, value } = this.props
onItemClick(value)
};
render () {
const { selectionState, title, count } = this.props
const mainClass = 'filters-table__item'
const classes = classnames(mainClass, {
[`${mainClass}--included`]: selectionState === 1,
[`${mainClass}--excluded`]: selectionState === -1
})
return (
<div className={classes} onClick={this.onItemClick}>
{title}
<span>{count}</span>
</div>
)
}
}
export default translate(['tabsContent'], { wait: true })(FilterItem)
@@ -0,0 +1,95 @@
import React from 'react';
import PropTypes from 'prop-types';
import { translate } from 'react-i18next';
import FilterGroup from './FilterGroup';
import { Button } from 'reactstrap';
import { arraymove } from '../../../common/helper';
export class FiltersTable extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
filters: PropTypes.object.isRequired,
selectedFilters: PropTypes.object.isRequired,
clearPending: PropTypes.object.isRequired,
pages: PropTypes.object.isRequired,
callbacks: PropTypes.object.isRequired
};
forEachGroup = (fn) => {
const { pages, filters, selectedFilters } = this.props;
const filterKeys = Object.keys(filters);
arraymove(
filterKeys,
filterKeys.indexOf('sentiment'),
filterKeys.indexOf('articleDate') + 1
);
return filterKeys.map((groupName) => {
return fn(
groupName,
filters[groupName],
pages[groupName],
selectedFilters[groupName] || {}
);
});
};
onRefineButton = () => {
this.props.callbacks.refine();
};
onClearAllButton = () => {
this.props.callbacks.clearAllFilters();
setTimeout(() => {
this.props.callbacks.refine();
}, 200);
};
render() {
const { callbacks, clearPending, t } = this.props;
return (
<div className="filters-table-container">
<div className="filters-table">
{this.forEachGroup(
(groupName, groupFilters, groupPage, selectedFilters) => {
return (
<FilterGroup
groupName={groupName}
key={groupName}
filters={groupFilters}
count={groupPage && groupPage.count}
totalCount={groupPage && groupPage.totalCount}
selectedFilters={selectedFilters}
clearPending={groupName in clearPending}
callbacks={callbacks}
/>
);
}
)}
</div>
<div className="text-center mt-2">
<Button
onClick={this.onRefineButton}
className="btn-icon mb-1 mr-1"
color="warning"
>
<i className="lnr lnr-sync btn-icon-wrapper"></i>
{t('filtersTable.refine')}
</Button>
<Button
onClick={this.onClearAllButton}
className="btn-icon mb-1"
color="secondary"
>
<i className="lnr lnr-trash btn-icon-wrapper"></i>
{t('filtersTable.clearAll')}
</Button>
</div>
</div>
);
}
}
export default translate(['common'], { wait: true })(FiltersTable);
+51
View File
@@ -0,0 +1,51 @@
import React from 'react';
import PropTypes from 'prop-types';
import { translate } from 'react-i18next';
function Footer({ t }) {
return (
<footer className="footer">
<ul className="footer__list">
<li className="footer__item">
<a
title={t('footer.privacyPolicy')}
target="_blank"
href="https://www.socialhose.io/en/legal/privacy"
className="footer__link"
>
{t('footer.privacyPolicy')}
</a>
</li>
<li className="footer__item">
<a
title={t('footer.acceptableUsePolicy')}
target="_blank"
href="https://www.socialhose.io/en/legal/acceptable-use"
className="footer__link"
>
{t('footer.acceptableUsePolicy')}
</a>
</li>
<li className="footer__item">
<a
title={t('footer.termsConditions')}
target="_blank"
href="https://www.socialhose.io/en/legal/terms"
className="footer__link"
>
{t('footer.termsConditions')}
</a>
</li>
<li className="footer__item">
{t('footer.copyright', { year: new Date().getFullYear() })}
</li>
</ul>
</footer>
);
}
Footer.propTypes = {
t: PropTypes.func.isRequired
};
export default translate(['loginApp'], { wait: true })(Footer);
@@ -0,0 +1,84 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormGroup, Label, CustomInput } from 'reactstrap';
import { Interpolate, translate } from 'react-i18next';
// need changes: err cannot be removed once triggered
function Checkbox({
t,
title,
hideTitle,
name,
formGroupClass,
required,
trueValue,
value,
disabled,
error,
description,
handleChange
}) {
function onChange(e) {
const { name, checked } = e.target;
let oppValue = null;
if (typeof trueValue === 'boolean') oppValue = false;
else if (typeof trueValue === 'number') oppValue = 0;
handleChange(name, checked ? trueValue : oppValue);
}
return (
<FormGroup className={formGroupClass}>
{!hideTitle && (
<Label>
{title}
{required ? <span className="text-danger">*</span> : null}
</Label>
)}
<CustomInput
id={name}
type="checkbox"
checked={trueValue === value}
title={title}
name={name}
value={value}
disabled={disabled}
label={description}
invalid={error}
onChange={onChange}
/>
{error === true ? (
<span className="text-danger">
<Interpolate t={t} i18nKey="messages.selectMsg" title={title} />
</span>
) : (
<span className="text-danger">{error}</span>
)}
</FormGroup>
);
}
Checkbox.defaultProps = {
handleChange: () => {},
trueValue: true,
disabled: false,
hideTitle: false,
formGroupClass: ''
};
Checkbox.propTypes = {
t: PropTypes.func,
title: PropTypes.string,
name: PropTypes.string.isRequired,
type: PropTypes.string,
formGroupClass: PropTypes.string,
value: PropTypes.bool,
hideTitle: PropTypes.bool,
trueValue: PropTypes.any,
description: PropTypes.any,
required: PropTypes.bool,
disabled: PropTypes.bool,
error: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
handleChange: PropTypes.func.isRequired
};
export default React.memo(translate(['common'], { wait: true })(Checkbox));
@@ -0,0 +1,140 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import {
Input as ReactInput,
FormGroup,
Label,
FormText,
InputGroup
} from 'reactstrap';
import { translate } from 'react-i18next';
function Input({
t,
title,
name,
required,
disabled,
value,
type,
options,
placeholder,
error,
description,
regex,
handleChange,
handleValidation,
validationMessage,
inputGroupAddon
}) {
function onChange(e) {
handleChange(e.target.name.trim(''), e.target.value);
}
function onValidate(e) {
if (!handleValidation) return;
const { value, name } = e.target;
let errorMsg = required ? null : undefined;
if (!value.replace(/\s/g, '').length && required) {
errorMsg = true;
} else if (value && regex && !regex.test(value)) {
errorMsg = validationMessage || t('messages.invalidMsg', { title });
}
handleValidation(name, errorMsg);
}
let optionsJSX;
if (type === 'select' && Array.isArray(options)) {
optionsJSX = [
<option key="empty" value="">
{t('messages.dropdownValue0', { title })}
</option>
];
options.map((option) =>
optionsJSX.push(
<option key={option.value} value={option.value}>
{option.label}
</option>
)
);
}
return (
<FormGroup>
<Label>
{title}
{required ? <span className="text-danger">*</span> : null}
</Label>
{inputGroupAddon ? (
<Fragment>
<InputGroup>
<ReactInput
title={title}
type={type}
name={name}
value={value}
placeholder={placeholder}
invalid={!!error}
onChange={onChange}
onBlur={onValidate}
disabled={disabled}
children={optionsJSX}
/>
{inputGroupAddon}
</InputGroup>
</Fragment>
) : (
<ReactInput
title={title}
type={type}
name={name}
value={value}
placeholder={placeholder}
invalid={!!error}
onChange={onChange}
onBlur={onValidate}
disabled={disabled}
children={optionsJSX}
/>
)}
{error === true ? (
<span className="text-danger">{t('messages.inputMsg', { title })}</span>
) : (
<span className="text-danger">{error}</span>
)}
{description && <FormText>{description}</FormText>}
</FormGroup>
);
}
Input.defaultProps = {
handleChange: () => {},
handleValidation: () => {},
disabled: false
};
Input.propTypes = {
t: PropTypes.func,
title: PropTypes.string,
name: PropTypes.string.isRequired,
type: PropTypes.string,
value: PropTypes.string,
placeholder: PropTypes.string,
validationMessage: PropTypes.string,
required: PropTypes.bool,
disabled: PropTypes.bool,
error: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.object,
PropTypes.string
]),
regex: PropTypes.object,
options: PropTypes.any,
description: PropTypes.any,
inputGroupAddon: PropTypes.any,
handleChange: PropTypes.func,
handleValidation: PropTypes.func
};
export default React.memo(translate(['common'], { wait: true })(Input));
@@ -0,0 +1,93 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormGroup, Label, CustomInput } from 'reactstrap';
import { translate } from 'react-i18next';
function RadioButton({
t,
title,
name,
required,
value,
disabled,
error,
inline,
options,
valueKey,
labelKey,
formClass,
handleChange
}) {
function onChange(e, value) {
let errorMsg = required ? null : undefined;
if (!value && required) {
errorMsg = t('messages.selectMsg', { title: name });
} else {
errorMsg = false;
}
handleChange(name, value, errorMsg);
}
return (
<FormGroup className={formClass}>
<Label className={inline ? 'mr-4' : ''}>
{title}
{inline ? ':' : null}
{required ? <span className="text-danger">*</span> : null}
</Label>
{options.map((o, i) => (
<CustomInput
key={`${name}${i}`}
id={`${name}${i}`}
type="radio"
inline={inline}
checked={o[valueKey] === value}
title={title}
name={name}
disabled={disabled}
label={o[labelKey]}
invalid={error}
onChange={function (e) {
onChange(e, o[valueKey]);
}}
/>
))}
{error === true ? (
<span className="text-danger">{t('messages.inputMsg', { title })}</span>
) : (
<span className="text-danger">{error}</span>
)}
</FormGroup>
);
}
RadioButton.defaultProps = {
options: [],
valueKey: 'value',
labelKey: 'label',
value: null,
handleChange: () => {},
handleValidation: () => {},
disabled: false
};
RadioButton.propTypes = {
t: PropTypes.func,
title: PropTypes.string,
name: PropTypes.string.isRequired,
type: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
formClass: PropTypes.string,
options: PropTypes.array,
labelKey: PropTypes.string,
valueKey: PropTypes.string,
required: PropTypes.bool,
disabled: PropTypes.bool,
inline: PropTypes.bool,
error: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
handleChange: PropTypes.func.isRequired
};
export default React.memo(
translate(['tabsContent'], { wait: true })(RadioButton)
);
@@ -0,0 +1,5 @@
import Input from './Input'
import Checkbox from './Checkbox'
import RadioButton from './RadioButton'
export { Input, Checkbox, RadioButton }
@@ -0,0 +1,16 @@
import React, { Component } from 'react'
export default class LoadersAdvanced extends Component {
render () {
return (
<div className="out-space">
<div className="lds-ellipsis">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
)
}
}
@@ -0,0 +1,70 @@
.out-space {
position: absolute;
background-color: #f7f7f7;
opacity: 0.9;
width: 100%;
height: 100%;
z-index: 99999;
display: -ms-flexbox;
display: flex;
-ms-flex-pack: center;
justify-content: center;
-ms-flex-align: center;
align-items: center;
}
.lds-ellipsis {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
}
.lds-ellipsis div {
position: absolute;
top: 33px;
width: 13px;
height: 13px;
border-radius: 50%;
background: $primary;
animation-timing-function: cubic-bezier(0, 1, 1, 0);
}
.lds-ellipsis div:nth-child(1) {
left: 8px;
animation: lds-ellipsis1 0.6s infinite;
}
.lds-ellipsis div:nth-child(2) {
left: 8px;
animation: lds-ellipsis2 0.6s infinite;
}
.lds-ellipsis div:nth-child(3) {
left: 32px;
animation: lds-ellipsis2 0.6s infinite;
}
.lds-ellipsis div:nth-child(4) {
left: 56px;
animation: lds-ellipsis3 0.6s infinite;
}
@keyframes lds-ellipsis1 {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
@keyframes lds-ellipsis3 {
0% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
@keyframes lds-ellipsis2 {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(24px, 0);
}
}
+30
View File
@@ -0,0 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import i18n from '../../i18n';
function Loading({
show = true,
message = i18n.t('common:commonWords.loading')
}) {
if (!show) {
return null;
}
return (
<div className="d-flex flex-column align-items-center justify-content-center text-muted p-5">
<span className="mb-2">
<FontAwesomeIcon icon={faSpinner} size="2x" pulse />
</span>
<p>{message}</p>
</div>
);
}
Loading.propTypes = {
show: PropTypes.bool,
message: PropTypes.string
};
export default Loading;
@@ -0,0 +1,27 @@
import React from 'react';
import PropTypes from 'prop-types';
import { IoIosInformationCircle } from 'react-icons/io';
import i18n from '../../i18n';
function NoRecords({
show = true,
message = i18n.t('common:messages.noResults')
}) {
if (!show) {
return null;
}
return (
<div className="d-flex flex-column align-items-center justify-content-center text-muted p-5">
<IoIosInformationCircle className="mb-2" fontSize="32px" />
<p>{message}</p>
</div>
);
}
NoRecords.propTypes = {
show: PropTypes.bool,
message: PropTypes.string
};
export default NoRecords;
@@ -0,0 +1,30 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Button } from 'reactstrap'
export default class LimitSelector extends React.Component {
static propTypes = {
pagerAction: PropTypes.func.isRequired,
limit: PropTypes.number.isRequired,
isCurrent: PropTypes.bool.isRequired
};
onClick = () => {
!this.props.isCurrent && this.props.pagerAction({limitByPage: this.props.limit})
};
render () {
let className = 'table-pager__limit'
if (this.props.isCurrent) {
className += ' ' + className + '--current'
}
return (
<Button color="primary" className={className} onClick={this.onClick}>
{this.props.limit}
</Button>
)
}
}
@@ -0,0 +1,34 @@
import React from 'react'
import PropTypes from 'prop-types'
import { PaginationItem, PaginationLink } from 'reactstrap'
export default class PageSelector extends React.Component {
static propTypes = {
pagerAction: PropTypes.func.isRequired,
index: PropTypes.number.isRequired,
isEllipsis: PropTypes.bool.isRequired,
isCurrent: PropTypes.bool.isRequired
};
onClick = () => {
!this.props.isCurrent &&
this.props.pagerAction({ currentPage: this.props.index })
};
render () {
if (this.props.isEllipsis) {
return <span className="table-pager__ellipsis">. . .</span>
} else {
// const currentClass = this.props.isCurrent
// ? ' table-pager__page--current'
// : ''
return (
<PaginationItem active={this.props.isCurrent}>
<PaginationLink onClick={this.onClick}>
{this.props.index}
</PaginationLink>
</PaginationItem>
)
}
}
}
@@ -0,0 +1,125 @@
import React from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'
import LimitSelector from './LimitSelector'
import PageSelector from './PageSelector'
import { ButtonGroup, Pagination, PaginationItem, PaginationLink } from 'reactstrap'
export class Pager extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
pagerAction: PropTypes.func.isRequired,
currentPage: PropTypes.number.isRequired,
numPages: PropTypes.number.isRequired,
limitByPage: PropTypes.number.isRequired,
hideLimitSelector: PropTypes.bool
};
static limits = [10, 25, 50, 100, 200];
onClickPrevPage = () => {
if (this.props.currentPage > 1) {
this.props.pagerAction({ currentPage: this.props.currentPage - 1 })
}
};
onClickNextPage = () => {
if (this.props.currentPage < this.props.numPages) {
this.props.pagerAction({ currentPage: this.props.currentPage + 1 })
}
};
getPaginationTemplate = (maxLength = 7) => {
const { numPages, currentPage } = this.props
let res = {}
if (numPages === 0) return res
//always show first, last and current page
res[1] = 1
res[numPages] = 1
res[currentPage] = 1
if (currentPage <= maxLength - 3) {
//show all from 1 to 5
for (let i = 2; i < maxLength - 1; i++) {
if (i < numPages) res[i] = 1
}
} else if (currentPage >= numPages - maxLength + 4) {
//show last five pages
for (let i = numPages - maxLength + 3; i < numPages; i++) {
res[i] = 1
}
} else {
//just show neighbours of current page
let shift = Math.floor((maxLength - 5) / 2)
for (let i = currentPage - shift; i <= currentPage + shift; i++) {
res[i] = 1
}
}
//and show ellipsis
if (numPages > 1) {
if (!res[2]) res[2] = 0
if (!res[numPages - 1]) res[numPages - 1] = 0
}
return res
};
render () {
const pages = this.getPaginationTemplate()
// const prevDisabledClass =
// this.props.currentPage > 1 ? '' : ' table-pager__page--disabled'
// const nextDisabledClass =
// this.props.currentPage < this.props.numPages
// ? ''
// : ' table-pager__page--disabled'
return (
<div className="table-pager">
<Pagination
className="pagination-rounded"
aria-label="Page navigation example"
>
<PaginationItem>
<PaginationLink onClick={this.onClickPrevPage} previous />
</PaginationItem>
{Object.keys(pages).map((index) => {
return (
<PageSelector
pagerAction={this.props.pagerAction}
key={index}
index={parseInt(index)}
isEllipsis={pages[index] === 0}
isCurrent={parseInt(index) === this.props.currentPage}
/>
)
})}
<PaginationItem>
<PaginationLink onClick={this.onClickNextPage} next />
</PaginationItem>
</Pagination>
{!this.props.hideLimitSelector && (
<div className="table-pager__limits">
<span>Show</span>
<ButtonGroup>
{Pager.limits.map((val) => {
return (
<LimitSelector
pagerAction={this.props.pagerAction}
key={val}
limit={val}
isCurrent={val === this.props.limitByPage}
/>
)
})}
</ButtonGroup>
</div>
)}
</div>
)
}
}
export default translate(['tabsContent'], { wait: true })(Pager)
@@ -0,0 +1,88 @@
import React, { Fragment } from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'
import classnames from 'classnames'
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'
export class PopupLayout extends React.Component {
static propTypes = {
className: PropTypes.string,
children: PropTypes.element.isRequired,
title: PropTypes.string,
showFooter: PropTypes.bool,
footer: PropTypes.element,
cancelText: PropTypes.string,
submitText: PropTypes.string,
submitColor: PropTypes.string,
onHide: PropTypes.func.isRequired,
onSubmit: PropTypes.func,
t: PropTypes.func.isRequired
};
hidePopup = () => {
this.props.onHide()
};
onSubmit = () => {
const { onSubmit } = this.props
onSubmit() && this.hidePopup()
};
getHeader () {
const { t, title = 'common:commonWords.Confirm' } = this.props
return (
<ModalHeader toggle={this.hidePopup}>
{t(title)}
</ModalHeader>
)
}
getFooter () {
const {
t,
footer,
showFooter = true,
cancelText = 'common:commonWords.Cancel',
submitText = 'common:commonWords.Confirm',
submitColor = 'primary'
} = this.props
if (!showFooter) return null
const hasFooter = !!footer
return (
<ModalFooter>
{hasFooter && footer}
{!hasFooter && (
<Fragment>
<Button color="light" onClick={this.hidePopup}>
{t(cancelText)}
</Button>
<Button color={submitColor} onClick={this.onSubmit}>
{t(submitText)}
</Button>
</Fragment>
)}
</ModalFooter>
)
}
render () {
const { children, className } = this.props
const classes = classnames('popup', className)
return (
<div className={classes}>
<Modal isOpen toggle={this.hidePopup} backdrop="static">
{this.getHeader()}
<ModalBody>{children}</ModalBody>
{this.getFooter()}
</Modal>
</div>
)
}
}
export default translate(['tabsContent', 'common'], { wait: true })(
PopupLayout
)
@@ -0,0 +1,83 @@
import React, { useEffect } from 'react'
import PropTypes from 'prop-types'
import Quill from 'quill'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
function QuillEditor({ id, children, reference, className }) {
const editorRef = reference
useEffect(() => {
// all custom font-sizes and font-families should set up in the whitelist first
const size = Quill.import('attributors/style/size')
const font = Quill.import('formats/font')
size.whitelist = ['10px', '13px', '16px', '18px', '24px', '32px', '48px']
font.whitelist = [
'roboto',
'lato',
'times',
'arial',
'courier',
'georgia',
'trebuchet',
'verdana'
]
Quill.register(size, true)
Quill.register(font, true)
//all custom labels and font-families are setting up via css, library works that way, in our case we setting up font-families and font-sizes
const toolbarOptions = [
[
{
font: [
'roboto',
'lato',
'times',
'arial',
'courier',
'georgia',
'trebuchet',
'verdana'
]
},
{
size: ['10px', '12px', '14px', '16px', '18px', '24px', '32px', '48px']
}
],
['bold', 'italic', 'underline', 'strike'],
[{ script: 'sub' }, { script: 'super' }],
[{ align: [] }],
[{ indent: '-1' }, { indent: '+1' }],
[{ list: 'ordered' }, { list: 'bullet' }],
['link', 'image'],
[{ color: [] }, { background: [] }],
['clean']
]
editorRef.current = new Quill(`#${id}`, {
theme: 'snow',
modules: {
toolbar: toolbarOptions
}
})
editorRef.current.focus()
}, [])
return (
<div id={id} className={className}>
{children}
</div>
)
}
QuillEditor.propTypes = {
id: PropTypes.string,
children: PropTypes.any,
reference: PropTypes.any,
className: PropTypes.string
}
export default QuillEditor
@@ -0,0 +1,246 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { translate } from 'react-i18next';
import { Row, Col, Progress } from 'reactstrap';
export class Restrictions extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
restrictions: PropTypes.object,
restrictionsIds: PropTypes.array
};
getBarColor(percentage) {
return percentage > 50
? percentage > 75
? 'danger'
: 'warning'
: 'success';
}
render() {
const { restrictions, restrictionsIds, t } = this.props;
if (!restrictions) return '';
let searchLicense = null;
let searchLicenseLimit = null;
let saveLicense = null;
let saveLicenseLimit = null;
let alerts = null;
let alertsLimit = null;
let webFeeds = null;
let webFeedsLimit = null;
let isAlerts = false;
restrictionsIds.map((id) => {
const restriction = restrictions[id];
if (id === 'alerts') {
alerts = restriction.current;
alertsLimit = restriction.limit;
isAlerts = true;
}
if (id === 'searchesPerDay') {
searchLicense = restriction.current;
searchLicenseLimit = restriction.limit;
}
if (id === 'savedFeeds') {
saveLicense = restriction.current;
saveLicenseLimit = restriction.limit;
}
if (id === 'webFeeds') {
webFeeds = restriction.current;
webFeedsLimit = restriction.limit;
}
});
const alertPerc = (alerts * 100) / alertsLimit;
const searchPerc = (searchLicense * 100) / searchLicenseLimit;
const feedPerc = (saveLicense * 100) / saveLicenseLimit;
const webFeedPerc = (webFeeds * 100) / webFeedsLimit;
return (
<Fragment>
{isAlerts ? (
<Row>
<Col md="6" xl="4">
<div className="card mb-3 widget-content">
<div className="widget-content-outer">
<div className="widget-content-wrapper">
<div className="widget-content-left">
<div className="widget-heading">
{t('restrictions.alertLicenses')}
</div>
<div className="widget-subheading">
{t('restrictions.perMonth')}
</div>
</div>
<div className="widget-content-right">
<div
className={`widget-numbers text-${this.getBarColor(
alertPerc
)}`}
>
{alertsLimit}
</div>
</div>
</div>
<div className="widget-progress-wrapper">
<Progress
className="progress-bar-sm progress-bar-animated-alt"
color={this.getBarColor(alertPerc)}
value={alertPerc}
/>
<div className="progress-sub-label">
{alerts} / {alertsLimit}
</div>
</div>
</div>
</div>
</Col>
{/* {restrictions.newsletters && (
<Col md="6" xl="4">
<div className="card mb-3 widget-content">
<div className="widget-content-outer">
<div className="widget-content-wrapper">
<div className="widget-content-left">
<div className="widget-heading">{t('restrictions.totalNewsltter')}</div>
</div>
<div className="widget-content-right">
<div className="widget-numbers text-primary">
{restrictions.newsletters.limit}
</div>
</div>
</div>
<div className="widget-progress-wrapper">
<Progress
className="progress-bar-sm progress-bar-animated-alt"
color="warning"
value={20}
/>
<div className="progress-sub-label">
{restrictions.newsletters.current} /{' '}
{restrictions.newsletters.limit}
</div>
</div>
</div>
</div>
</Col>
)} */}
<Col md="6" xl="4">
<div className="card mb-3 widget-content">
<div className="widget-content-outer">
<div className="widget-content-wrapper">
<div className="widget-content-left">
<div className="widget-heading">
{t('restrictions.webfeedLicenses')}
</div>
<div className="widget-subheading">
{t('restrictions.perMonth')}
</div>
</div>
<div className="widget-content-right">
<div
className={`widget-numbers text-${this.getBarColor(
webFeedPerc
)}`}
>
{webFeedsLimit}
</div>
</div>
</div>
<div className="widget-progress-wrapper">
<Progress
className="progress-bar-sm progress-bar-animated-alt"
color={this.getBarColor(webFeedPerc)}
value={webFeedPerc}
/>
<div className="progress-sub-label">
{webFeeds} / {webFeedsLimit}
</div>
</div>
</div>
</div>
</Col>
</Row>
) : (
<Row data-tour="search-licenses">
<Col md="6" xl="4">
<div className="card mb-3 widget-content">
<div className="widget-content-outer">
<div className="widget-content-wrapper">
<div className="widget-content-left">
<div className="widget-heading">
{t('restrictions.searchLicenses')}
</div>
<div className="widget-subheading">
{t('restrictions.perDay')}
</div>
</div>
<div className="widget-content-right">
<div
className={`widget-numbers text-${this.getBarColor(
searchPerc
)}`}
>
{searchLicenseLimit}
</div>
</div>
</div>
<div className="widget-progress-wrapper">
<Progress
className="progress-bar-sm progress-bar-animated-alt"
color={this.getBarColor(searchPerc)}
value={searchPerc}
/>
<div className="progress-sub-label">
{searchLicense} / {searchLicenseLimit}
</div>
</div>
</div>
</div>
</Col>
<Col md="6" xl="4">
<div className="card mb-3 widget-content">
<div className="widget-content-outer">
<div className="widget-content-wrapper">
<div className="widget-content-left">
<div className="widget-heading">
{t('restrictions.feedLicenses')}
</div>
<div className="widget-subheading">
{t('restrictions.perMonth')}
</div>
</div>
<div className="widget-content-right">
<div
className={`widget-numbers text-${this.getBarColor(
feedPerc
)}`}
>
{saveLicenseLimit}
</div>
</div>
</div>
<div className="widget-progress-wrapper">
<Progress
className="progress-bar-sm progress-bar-animated-alt"
color={this.getBarColor(feedPerc)}
value={feedPerc}
/>
<div className="progress-sub-label">
{saveLicense} / {saveLicenseLimit}
</div>
</div>
</div>
</div>
</Col>
</Row>
)}
</Fragment>
);
}
}
export default translate(['tabsContent'], { wait: true })(Restrictions);
@@ -0,0 +1,28 @@
import React from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'
export class CheckboxCell extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
id: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
checked: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired
};
onChange = () => {
this.props.onChange(this.props.id)
};
render () {
return (
<input type="checkbox" checked={this.props.checked} onChange={this.onChange} />
)
}
}
export default translate(['tabsContent'], { wait: true })(CheckboxCell)
@@ -0,0 +1,26 @@
import React from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'
import { Button } from 'reactstrap'
export class DeleteButton extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
id: PropTypes.number.isRequired,
onDelete: PropTypes.func.isRequired
}
onDelete = () => {
this.props.onDelete(this.props.id)
}
render() {
return (
<Button color="link" className="text-danger p-0" onClick={this.onDelete}>
<i className="lnr lnr-trash" />
</Button>
)
}
}
export default translate(['tabsContent'], { wait: true })(DeleteButton)
@@ -0,0 +1,36 @@
import React from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'
import { Button } from 'reactstrap';
export class LinkCell extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
item: PropTypes.object.isRequired,
children: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element
]),
onClick: PropTypes.func.isRequired
};
onClick = () => {
this.props.onClick(this.props.item)
};
render () {
return (
<Button
type="button"
color="link"
className="btn-anchor"
onClick={this.onClick}
>
{this.props.children}
</Button>
)
}
}
export default translate(['tabsContent'], { wait: true })(LinkCell)
@@ -0,0 +1,26 @@
import React from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'
export class SortableTh extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
title: PropTypes.string.isRequired
};
render () {
const { t, title } = this.props
return (
<div className="sortable-th">
{t(title)}
<i className="sorting-icon fa fa-sort" />
<i className="sorting-icon fa fa-sort-asc" />
<i className="sorting-icon fa fa-sort-desc" />
</div>
)
}
}
export default translate(['tabsContent'], { wait: true })(SortableTh)
@@ -0,0 +1,163 @@
import React from 'react';
import PropTypes from 'prop-types';
import { translate, Interpolate } from 'react-i18next';
import ReactTable from 'react-table';
import 'react-table/react-table.css';
import Pager from '../Pager/Pager';
import { Row, Col, Card, CardBody, CardTitle } from 'reactstrap';
import LoadersAdvanced from '../Loader/Loader';
// const steps = [
// { name: 'Account Information' },
// { name: 'Payment Information' },
// { name: 'Finish Wizard' }
// ]
export class Table extends React.Component {
static propTypes = {
t: PropTypes.func,
data: PropTypes.array.isRequired,
columns: PropTypes.array.isRequired,
totalCount: PropTypes.number.isRequired,
showTotalCount: PropTypes.bool,
noCard: PropTypes.bool,
limit: PropTypes.number.isRequired,
page: PropTypes.number.isRequired,
isLoading: PropTypes.bool.isRequired,
onFetchData: PropTypes.func.isRequired,
onRowClick: PropTypes.func,
cardTitle: PropTypes.string
};
onFetchData = (state) => {
this.props.onFetchData(
state.page,
state.pageSize,
state.sorted,
state.filtered
);
};
onPageAction = (pageState) => {
const { totalCount } = this.props;
const gridState = this.refs.grid.state;
const { page, pageSize, sorted, filtered } = gridState;
let state = { page, pageSize, sorted, filtered };
if (pageState.limitByPage) {
state.pageSize = pageState.limitByPage;
}
if (pageState.currentPage) {
state.page = pageState.currentPage - 1;
}
if (totalCount < state.pageSize) {
state.page = 0;
}
this.onFetchData(state);
};
getPagination = () => {
const { showTotalCount = false, totalCount, page, limit } = this.props;
const numPages = Math.ceil(totalCount / limit);
return totalCount > 0 ? (
<div className="px-3">
{showTotalCount && (
<div className="results-table-count-info">
<Interpolate
i18nKey="sourceIndexTab.showingCounter"
startCount={limit * page - (limit - 1)}
endCount={limit * page}
totalCount={totalCount}
/>
</div>
)}
<Pager
pagerAction={this.onPageAction}
currentPage={page}
limitByPage={limit}
numPages={numPages}
/>
</div>
) : null;
};
getLoading = (props) => {
if (!props.loading) return null;
return <LoadersAdvanced />;
// <div className="component-loader"></div>;
};
getTrProps = (state, rowInfo, column, instance) => {
const { onRowClick } = this.props;
let result = {};
if (onRowClick) {
result.onClick = (e) => {
onRowClick(e, state, rowInfo, column, instance);
};
}
return result;
};
NoDataConst = () => (
<div className="p-4 text-center text-black-50">
{this.props.t('common:messages.noRows', {
defaultValue: 'No rows found'
})}
</div>
);
render() {
const {
data,
columns,
page,
limit,
isLoading,
cardTitle,
noCard
} = this.props;
const renderTable = (
<ReactTable
ref="grid"
className="cw-grid -striped"
data={data}
columns={columns}
minRows={0}
manual
page={page - 1}
pageSize={limit}
defaultPageSize={10}
loading={isLoading}
PaginationComponent={this.getPagination}
LoadingComponent={this.getLoading}
onFetchData={this.onFetchData}
getTrProps={this.getTrProps}
NoDataComponent={this.NoDataConst}
/>
);
if (noCard) {
return renderTable;
}
return (
<Row>
<Col md="12">
<Card className="main-card mb-3">
<CardBody>
{cardTitle && <CardTitle>{cardTitle}</CardTitle>}
{renderTable}
</CardBody>
</Card>
</Col>
</Row>
);
}
}
export default translate(['tabsContent'], { wait: true })(Table);
@@ -0,0 +1,50 @@
import React from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'
import { ButtonGroup, Button } from 'reactstrap'
export class Toggler extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
turnOnAction: PropTypes.func.isRequired,
turnOffAction: PropTypes.func.isRequired,
state: PropTypes.bool.isRequired,
enabledText: PropTypes.string.isRequired,
disabledText: PropTypes.string.isRequired
};
onOnClick = () => {
!this.props.state && this.props.turnOnAction(this.props.id)
};
onOffClick = () => {
this.props.state && this.props.turnOffAction(this.props.id)
};
render () {
const { enabledText, disabledText, state, t } = this.props
return (
<ButtonGroup size="sm">
<Button
outline
color="success"
onClick={this.onOnClick}
active={state}
>
{t('toggler.' + enabledText)}
</Button>
<Button
outline
color="success"
onClick={this.onOffClick}
active={!state}
>
{t('toggler.' + disabledText)}
</Button>
</ButtonGroup>
)
}
}
export default translate(['tabsContent'], { wait: true })(Toggler)
@@ -0,0 +1,51 @@
import React from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'
export class TableHeaderSortItem extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
isSorted: PropTypes.bool.isRequired,
sortDirection: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
width: PropTypes.string,
sortAction: PropTypes.func.isRequired,
fieldName: PropTypes.string.isRequired,
textAlign: PropTypes.string
};
onClick = () => {
const sortDirection = (!this.props.isSorted || this.props.sortDirection === 'desc') ? 'asc' : 'desc'
this.props.sortAction({sortByField: this.props.fieldName, sortDirection: sortDirection})
};
render () {
const {t, isSorted, sortDirection} = this.props
return (
<th style={{
textAlign: this.props.textAlign || '',
width: this.props.width || 'auto'
}}>
{t(this.props.title)}
{!isSorted &&
<i className="sorting-icon fa fa-sort" onClick={this.onClick}></i>
}
{isSorted && sortDirection === 'asc' &&
<i className="sorting-icon fa fa-sort-asc" onClick={this.onClick}></i>
}
{isSorted && sortDirection === 'desc' &&
<i className="sorting-icon fa fa-sort-desc" onClick={this.onClick}></i>
}
</th>
)
}
}
export default translate(['tabsContent'], { wait: true })(TableHeaderSortItem)
@@ -0,0 +1,53 @@
import React from 'react'
import PropTypes from 'prop-types'
import { translate, Interpolate } from 'react-i18next'
export class Alert extends React.Component {
static propTypes = {
alert: PropTypes.func.isRequired,
removeAlert: PropTypes.func.isRequired,
t: PropTypes.func.isRequired
};
componentDidMount = () => {
setTimeout(this.closeAlert, 5000)
};
closeAlert = () => {
const { removeAlert, alert } = this.props
removeAlert(alert.id)
};
onClose = (e) => {
e.preventDefault()
this.closeAlert()
};
render () {
const { alert } = this.props
const interpolateParameters = alert.parameters || {}
return (
<div className={'alert ' + alert.type}>
<div className="alert__head">
<h2 className="alert__title">{alert.type}</h2>
<a href="#" className="alert__close-btn" onClick={this.onClose}>
<i className="fa fa-times"> </i>
</a>
</div>
<div className="alert__body">
<p className="alert__text">
<Interpolate
i18nKey={'alerts.' + alert.type + '.' + alert.transKey}
{...interpolateParameters}
options={{defaultValue: alert.message || 'Unknown error'}}
/>
</p>
</div>
</div>
)
}
}
export default translate(['common'], { wait: true })(Alert)
@@ -0,0 +1,40 @@
import React from 'react'
import PropTypes from 'prop-types'
import Alert from './Alert'
export class Alerts extends React.Component {
static propTypes = {
alerts: PropTypes.array.isRequired,
removeAlert: PropTypes.func.isRequired
};
forEachAlert = (callback) => {
return this.props.alerts
.map((alert) => {
return (typeof alert === 'string') ? {message: alert} : alert
})
.map(callback)
};
render () {
const { alerts, removeAlert } = this.props
if (alerts.length <= 0) return null
return (
<div className="alerts-container">
{this.forEachAlert((alert, i) => {
return (
<Alert
key={i}
removeAlert={removeAlert}
alert={alert}
/>
)
})}
</div>
)
}
}
export default Alerts
@@ -0,0 +1,138 @@
export const BarToolbox = {
feature: {
saveAsImage: {
name: 'Socialhose Chart',
show: true,
title: 'Save as Image',
// name: 'Results Over Time', // need to set dynamic
type: 'jpeg',
backgroundColor: '#FFFFFF',
pixelRatio: 2
},
dataZoom: {
show: true,
title: {
zoom: 'Zoom',
back: 'Restore Zoom'
}
},
dataView: {
show: true,
title: 'View Data',
readOnly: true,
lang: ['View Data', 'Close', 'Refresh']
},
magicType: {
show: true,
title: {
line: 'Line Chart',
bar: 'Bar Chart',
tiled: 'Tiled Chart'
},
type: ['line', 'bar', 'tiled']
},
restore: { show: true, title: 'Restore' }
}
}
export const PieToolbox = {
feature: {
saveAsImage: {
name: 'Socialhose Chart',
show: true,
title: 'Save as Image',
// name: 'Share of Topics', // need to set dynamic
type: 'jpeg',
backgroundColor: '#FFFFFF',
pixelRatio: 2
},
dataView: {
show: true,
readOnly: true,
title: 'View Data',
lang: ['View Data', 'Close', 'Refresh']
},
restore: { show: true, title: 'Restore' }
}
}
export function getBarOptions(data, labels) {
return {
tooltip: {
show: true
},
toolbox: BarToolbox,
xAxis: {
type: 'category',
data: labels
},
yAxis: {
type: 'value'
},
series: data,
legend: {
y: 'bottom',
show: true
}
}
}
export function getPieOptions(data) {
return {
tooltip: {
show: true
},
toolbox: PieToolbox,
series: {
type: 'pie',
data: data,
label: {
position: 'outer',
alignTo: 'none',
bleedMargin: 5
},
top: '10%',
bottom: '10%'
},
legend: {
top: 'bottom',
show: true
}
}
}
export const WordCloudOptions = {
type: 'wordCloud',
shape: 'circle',
sizeRange: [12, 35],
rotationRange: [0, 0],
width: '100%',
height: '100%',
top: '10%',
bottom: '10%',
drawOutOfBound: false,
gridSize: 8,
textStyle: {
normal: {
fontFamily: 'sans-serif',
fontWeight: 'bold',
// Color can be a callback function or a color string
color: function () {
// Random color
return (
'rgb(' +
[
Math.round(Math.random() * 160),
Math.round(Math.random() * 160),
Math.round(Math.random() * 160)
].join(',') +
')'
)
}
},
emphasis: {
shadowBlur: 1,
shadowColor: '#333'
}
}
}
@@ -0,0 +1,70 @@
import React, { useEffect, useRef, useState } from 'react'
import PropTypes from 'prop-types'
import echarts from 'echarts'
import cx from 'classnames'
function ECharts(props) {
const { options, style, className, loading, message } = props
const [chart, setChart] = useState(null)
const chartRef = useRef()
useEffect(() => {
const chart = echarts.init(chartRef.current, 'westeros')
chart.setOption({ ...options, resizeObserver }, true) // second param is for 'noMerge'
setChart(chart)
if (resizeObserver) resizeObserver.observe(chartRef.current)
}, [options])
useEffect(() => {
if (!chart) {
return
}
if (loading) {
chart.showLoading()
return
}
chart.hideLoading()
}, [chart, loading])
useEffect(() => {
if (chart && options && message) {
chart.clear()
}
}, [message])
const newStyle = {
height: 350,
...style
}
return (
<div className="echarts-parent position-relative">
<div
ref={chartRef}
style={newStyle}
className={cx('echarts-react', className)}
/>
{message ? <div className="no-data">{message}</div> : null}
</div>
)
}
ECharts.propTypes = {
loading: PropTypes.bool,
options: PropTypes.any,
className: PropTypes.string,
style: PropTypes.object,
message: PropTypes.any
}
const resizeObserver = new window.ResizeObserver((entries) => {
entries.map(({ target }) => {
const instance = echarts.getInstanceByDom(target)
if (instance) {
instance.resize()
}
})
})
export default React.memo(ECharts)
@@ -0,0 +1,382 @@
{
"color": ["#516b91", "#59c4e6", "#edafda", "#93b7e3", "#a5e7f0", "#cbb0e3"],
"backgroundColor": "rgba(0,0,0,0)",
"textStyle": {},
"title": {
"textStyle": {
"color": "#516b91"
},
"subtextStyle": {
"color": "#93b7e3"
}
},
"line": {
"itemStyle": {
"borderWidth": "2"
},
"lineStyle": {
"width": "2"
},
"symbolSize": "6",
"symbol": "emptyCircle",
"smooth": true
},
"radar": {
"itemStyle": {
"borderWidth": "2"
},
"lineStyle": {
"width": "2"
},
"symbolSize": "6",
"symbol": "emptyCircle",
"smooth": true
},
"bar": {
"itemStyle": {
"barBorderWidth": 0,
"barBorderColor": "#ccc"
}
},
"pie": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"scatter": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"boxplot": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"parallel": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"sankey": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"funnel": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"gauge": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"candlestick": {
"itemStyle": {
"color": "#edafda",
"color0": "transparent",
"borderColor": "#d680bc",
"borderColor0": "#8fd3e8",
"borderWidth": "2"
}
},
"graph": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"lineStyle": {
"width": 1,
"color": "#aaaaaa"
},
"symbolSize": "6",
"symbol": "emptyCircle",
"smooth": true,
"color": ["#516b91", "#59c4e6", "#edafda", "#93b7e3", "#a5e7f0", "#cbb0e3"],
"label": {
"color": "#eeeeee"
}
},
"map": {
"itemStyle": {
"normal": {
"areaColor": "#f3f3f3",
"borderColor": "#516b91",
"borderWidth": 0.5
},
"emphasis": {
"areaColor": "#a5e7f0",
"borderColor": "#516b91",
"borderWidth": 1
}
},
"label": {
"normal": {
"textStyle": {
"color": "#000"
}
},
"emphasis": {
"textStyle": {
"color": "#516b91"
}
}
}
},
"geo": {
"itemStyle": {
"normal": {
"areaColor": "#f3f3f3",
"borderColor": "#516b91",
"borderWidth": 0.5
},
"emphasis": {
"areaColor": "#a5e7f0",
"borderColor": "#516b91",
"borderWidth": 1
}
},
"label": {
"normal": {
"textStyle": {
"color": "#000"
}
},
"emphasis": {
"textStyle": {
"color": "#516b91"
}
}
}
},
"categoryAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#999999"
}
},
"splitLine": {
"show": true,
"lineStyle": {
"color": ["#eeeeee"]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": ["rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)"]
}
}
},
"valueAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#999999"
}
},
"splitLine": {
"show": true,
"lineStyle": {
"color": ["#eeeeee"]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": ["rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)"]
}
}
},
"logAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#999999"
}
},
"splitLine": {
"show": true,
"lineStyle": {
"color": ["#eeeeee"]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": ["rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)"]
}
}
},
"timeAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#999999"
}
},
"splitLine": {
"show": true,
"lineStyle": {
"color": ["#eeeeee"]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": ["rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)"]
}
}
},
"toolbox": {
"iconStyle": {
"normal": {
"borderColor": "#999999"
},
"emphasis": {
"borderColor": "#516b91"
}
}
},
"legend": {
"textStyle": {
"color": "#999999"
}
},
"tooltip": {
"axisPointer": {
"lineStyle": {
"color": "#cccccc",
"width": 1
},
"crossStyle": {
"color": "#cccccc",
"width": 1
}
}
},
"timeline": {
"lineStyle": {
"color": "#8fd3e8",
"width": 1
},
"itemStyle": {
"normal": {
"color": "#8fd3e8",
"borderWidth": 1
},
"emphasis": {
"color": "#8fd3e8"
}
},
"controlStyle": {
"normal": {
"color": "#8fd3e8",
"borderColor": "#8fd3e8",
"borderWidth": 0.5
},
"emphasis": {
"color": "#8fd3e8",
"borderColor": "#8fd3e8",
"borderWidth": 0.5
}
},
"checkpointStyle": {
"color": "#8fd3e8",
"borderColor": "rgba(138,124,168,0.37)"
},
"label": {
"normal": {
"textStyle": {
"color": "#8fd3e8"
}
},
"emphasis": {
"textStyle": {
"color": "#8fd3e8"
}
}
}
},
"visualMap": {
"color": ["#516b91", "#59c4e6", "#a5e7f0"]
},
"dataZoom": {
"backgroundColor": "rgba(0,0,0,0)",
"dataBackgroundColor": "rgba(255,255,255,0.3)",
"fillerColor": "rgba(167,183,204,0.4)",
"handleColor": "#a7b7cc",
"handleSize": "100%",
"textStyle": {
"color": "#333333"
}
},
"markPoint": {
"label": {
"color": "#eeeeee"
},
"emphasis": {
"label": {
"color": "#eeeeee"
}
}
}
}
@@ -0,0 +1,65 @@
import { useState, useCallback } from 'react';
import { cloneDeep } from 'lodash';
function useForm({ errors: initialErrors, ...rest }) {
const [form, setForm] = useState(rest);
const [errors, setErrors] = useState(initialErrors);
const handleChange = useCallback((name, value, err = undefined) => {
setForm((form) => ({
...form,
[name]: value
}));
if (errors) {
setErrors((errors) => ({
...errors,
[name]: err !== undefined ? err : errors[name]
}));
}
}, []);
const handleValidation = useCallback((name, err) => {
setErrors((errors) => ({
...errors,
[name]: err
}));
}, []);
const validateSubmit = useCallback(() => {
let failed;
for (let val in errors) {
const fieldError = errors[val];
if (fieldError) {
failed = true;
} else if (fieldError === null && !form[val] && form[val] !== 0) {
failed = true;
handleValidation(val, true);
}
}
if (failed) {
return false;
} else {
return cloneDeep(form);
}
}, [form, errors, handleValidation]);
function resetForm(values = {}) {
// eslint-disable-next-line no-unused-vars
const { errors, ...formValues } = values;
setForm(formValues || rest);
setErrors(initialErrors);
}
return {
form,
handleChange,
handleValidation,
setForm,
validateSubmit,
errors,
resetForm
};
}
export default useForm;
@@ -0,0 +1,16 @@
import { useRef, useEffect } from 'react';
function useIsMounted() {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
return isMounted;
}
export default useIsMounted;
@@ -0,0 +1,45 @@
import { useEffect } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import { isLive } from '../../../common/constants';
function usePageTracking() {
const location = useLocation();
const history = useHistory();
useEffect(() => {
// google analytics
if (window.gtag && isLive) {
setTimeout(() => {
window.gtag('event', 'page_view', {
page_location: window.location.href,
page_path: location.pathname + location.search
// page_title: '<Page Title>',
});
}, 0);
}
}, [window.gtag, location]);
useEffect(() => {
isLive &&
history.listen(() => {
// Added to history listen to prevent first pageview call which is called by hubspot tracking script
setTimeout(() => { // to wait until document title updates
const _hsq = window._hsq;
if (location && _hsq) {
// hubspot tracking
_hsq.push(['setPath', location.pathname + location.search]);
_hsq.push(['trackPageView']);
}
if (location && window.lintrk) {
// linkedin insight tracking
window.lintrk('track');
}
}, 0);
});
}, []);
return null;
}
export default usePageTracking;
@@ -0,0 +1,35 @@
import React, { useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import { Modal } from 'reactstrap'
function ModalPopup(props) {
const { children, modalProps, show, hideModal, handled } = props
const [open, setOpen] = useState(true)
useEffect(() => setOpen(false)) // when unmounts
function toggle() {
setOpen((prev) => !prev)
}
return (
<Modal
isOpen={show || open}
toggle={handled ? hideModal : toggle}
backdrop="static"
{...modalProps}
>
{children && (handled ? children : children(toggle, open))}
</Modal>
)
}
ModalPopup.propTypes = {
handled: PropTypes.bool,
show: PropTypes.bool,
hideModal: PropTypes.func,
children: PropTypes.func.isRequired,
modalProps: PropTypes.object
}
export default React.memo(ModalPopup)