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,146 @@
import React, { Fragment } from 'react'
import GenericTable from '../common/GenericTable'
import { translate } from 'react-i18next'
import PropTypes from 'prop-types'
import { NOTIFICATION_TABLES } from '../../../../../redux/modules/appState/share/tabs'
import SortableTh from '../../../../common/Table/SortableTh'
import { ButtonGroup, Button } from 'reactstrap'
export class MyEmailsTable extends GenericTable {
static propTypes = {
t: PropTypes.func.isRequired,
tableState: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired,
tableActions: PropTypes.object.isRequired,
restrictions: PropTypes.object,
deleteSingleText: PropTypes.string.isRequired,
deleteMultipleText: PropTypes.string.isRequired
};
togglerOnAction = (itemId) => {
this.props.tableActions.toggleActive([itemId], true)
};
togglerOffAction = (itemId) => {
this.props.tableActions.toggleActive([itemId], false)
};
nameClickAction = (item) => {
const { actions } = this.props
actions.startEditNotification(item, NOTIFICATION_TABLES.MY_EMAILS)
};
onPublishButtonClick = () => {
const { tableState, tableActions } = this.props
tableActions.togglePublish(tableState.selectedIds, true)
};
onUnPublishButtonClick = () => {
const { tableState, tableActions } = this.props
tableActions.togglePublish(tableState.selectedIds, false)
};
_recipientsFormat (recipients) {
if (recipients.length === 1) {
return recipients[0].email
}
return `${recipients.length} ${this.props.t(
'notificationsTab.recipients'
)}`
}
defineColumns () {
const { t } = this.props
const colDefinitions = super.defineColumns()
return {
...colDefinitions,
active: this.createTogglerColumn(
'notificationsTab.action',
'active',
'active',
'paused',
this.togglerOnAction,
this.togglerOffAction
),
Recipients: {
sortable: false,
Header: t('notificationsTab.Recipients'),
accessor: (item) => this._recipientsFormat(item.recipients),
width: 110
},
published: {
Header: <SortableTh title="notificationsTab.published" />,
accessor: (item) =>
item.published
? t('common:commonWords.Yes')
: t('common:commonWords.No'),
width: 100
}
}
}
getColumns () {
return [
'selectCheckbox',
'name',
'type',
'published',
'ScheduledTimes',
'sourcesCount',
'Recipients',
'active',
'delete'
]
}
getActionsPanel () {
const { t, restrictions } = this.props
return (
<Fragment>
{this.getRestrictions(restrictions)}
<ButtonGroup className="mb-3">
<Button
color="secondary"
onClick={this.onActivateButtonClick}
>
<i className="fa fa-play for-small mr-1"> </i>{" "}
{t('notificationsTab.activate')}
</Button>
<Button
color="secondary"
onClick={this.onPauseButtonClick}
>
<i className="fa fa-pause for-small mr-1"> </i>{" "}
{t('notificationsTab.pause')}
</Button>
<Button
color="secondary"
onClick={this.onDeleteButtonClick}
>
<i className="fa fa-trash for-small mr-1"> </i>{" "}
{t('notificationsTab.delete')}
</Button>
<Button
color="secondary"
onClick={this.onPublishButtonClick}
>
<i className="fa fa-upload for-small mr-1"> </i>{" "}
{t('notificationsTab.publish')}
</Button>
<Button
color="secondary"
onClick={this.onUnPublishButtonClick}
>
<i className="fa fa-ban for-small mr-1"> </i>{" "}
{t('notificationsTab.unpublish')}
</Button>
</ButtonGroup>
</Fragment>
)
}
}
export default translate(['tabsContent'], { wait: true })(MyEmailsTable)
@@ -0,0 +1,26 @@
import React from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'
import { Button } from 'reactstrap'
class Navigation extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
actions: PropTypes.object.isRequired
};
backToTables = () => {
this.props.actions.switchShareSubScreen('notifications', 'tables')
};
render () {
return (
<Button className="btn-wide mb-2" size="sm" color="info" onClick={this.backToTables}>
<i className="lnr lnr-chevron-left"> </i>
</Button>
)
}
}
export default translate(['tabsContent'], { wait: true })(Navigation)
@@ -0,0 +1,106 @@
import React from 'react'
import PropTypes from 'prop-types'
import TopBar from './TopBar'
import Navigation from './Navigation'
import AlertForm from './forms/AlertForm'
import {NOTIFICATION_TABLES, NOTIFICATION_SUBSCREENS} from '../../../../../redux/modules/appState/share/tabs'
import MyEmailsTable from './MyEmailsTable'
import PublishedEmailsTable from './PublishedEmailsTable'
import {withRouter} from 'react-router-dom'
import reduxConnect from '../../../../../redux/utils/connect'
import {compose} from 'redux'
import { setDocumentData } from '../../../../../common/helper'
class NotificationsSubTab extends React.Component {
static propTypes = {
store: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired
};
_shareState = () => this.props.store.appState.share;
_authState = () => this.props.store.common.auth;
componentDidMount() {
setDocumentData('title', 'Alerts | Share')
}
componentWillUnmount() {
setDocumentData('title')
}
render () {
const { actions } = this.props
const shareState = this._shareState()
const {user: {restrictions}} = this._authState()
const { subScreenVisible, tableVisible } = shareState.tabs.notifications
return (
<div className="notifications-tab">
{subScreenVisible === NOTIFICATION_SUBSCREENS.TABLES &&
<div>
<TopBar
tables={[NOTIFICATION_TABLES.MY_EMAILS, NOTIFICATION_TABLES.PUBLISHED]}
tableVisible={tableVisible}
actions={actions}
/>
{tableVisible === NOTIFICATION_TABLES.MY_EMAILS &&
<MyEmailsTable
tableState={shareState.tables[tableVisible]}
restrictions={restrictions && restrictions.limits}
actions={actions}
tableActions={actions.shareTables[tableVisible]}
deleteSingleText='alert'
deleteMultipleText='alerts'
/>
}
{tableVisible === NOTIFICATION_TABLES.PUBLISHED &&
<PublishedEmailsTable
tableState={shareState.tables[tableVisible]}
restrictions={restrictions && restrictions.limits}
actions={actions}
tableActions={actions.shareTables[tableVisible]}
deleteSingleText='alert'
deleteMultipleText='alerts'
/>
}
</div>
}
{(subScreenVisible === NOTIFICATION_SUBSCREENS.ALERT_FORM || subScreenVisible === NOTIFICATION_SUBSCREENS.NEWSLETTER_FORM) &&
<Navigation actions={actions} />
}
{subScreenVisible === NOTIFICATION_SUBSCREENS.ALERT_FORM &&
<AlertForm
state={shareState.forms.alert}
switchShareSubScreen={actions.switchShareSubScreen}
actions={actions.shareForms.alert}
/>
}
{/* {subScreenVisible === NOTIFICATION_SUBSCREENS.NEWSLETTER_FORM &&
<NewsletterForm
state={shareState.forms.newsletter}
switchShareSubScreen={actions.switchShareSubScreen}
actions={actions.shareForms.newsletter}
/>
} */}
</div>
)
}
}
const applyDecorators = compose(
withRouter,
reduxConnect()
)
export default applyDecorators(NotificationsSubTab)
@@ -0,0 +1,89 @@
import React, { Fragment } from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'
import GenericTable from '../common/GenericTable'
import {NOTIFICATION_TABLES} from '../../../../../redux/modules/appState/share/tabs'
import SortableTh from '../../../../common/Table/SortableTh'
import { Button, ButtonGroup } from 'reactstrap'
class PublishedEmailsTable extends GenericTable {
static propTypes = {
t: PropTypes.func.isRequired,
tableState: PropTypes.object.isRequired,
restrictions: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired,
tableActions: PropTypes.object.isRequired,
deleteSingleText: PropTypes.string.isRequired,
deleteMultipleText: PropTypes.string.isRequired
};
onSubscribeButtonClick = () => {
const { tableState, tableActions } = this.props
tableActions.toggleSubscribe(tableState.selectedIds, true)
};
onUnSubscribeButtonClick = () => {
const { tableState, tableActions } = this.props
tableActions.toggleSubscribe(tableState.selectedIds, false)
};
togglerOnAction = (itemId) => {
this.props.tableActions.toggleSubscribe([itemId], true)
};
togglerOffAction = (itemId) => {
const {tableState, tableActions, actions} = this.props
const notification = tableState.data.find(item => item.id === itemId)
if (notification.allowUnsubscribe) {
tableActions.toggleSubscribe([itemId], false)
} else {
actions.addAlert({type: 'error', transKey: 'cannotUnsubscribe'})
}
};
nameClickAction = (item) => {
const { actions } = this.props
actions.startEditNotification(item, NOTIFICATION_TABLES.PUBLISHED)
};
defineColumns () {
const {t} = this.props
const colDefinitions = super.defineColumns()
return {
...colDefinitions,
'subscribed': this.createTogglerColumn('notificationsTab.action', 'subscribed', 'subscribed', 'unsubscribed', this.togglerOnAction, this.togglerOffAction),
'active': {
Header: <SortableTh title='notificationsTab.status' />,
accessor: item => item.active ? t('notificationsTab.active') : t('notificationsTab.paused'),
width: 100
}
}
};
getColumns () {
return ['selectCheckbox', 'name', 'type', 'owner', 'ScheduledTimes', 'active', 'subscribed']
}
getActionsPanel () {
const {t, restrictions} = this.props
return (
<Fragment>
{this.getRestrictions(restrictions)}
<ButtonGroup className="mb-3">
<Button onClick={this.onSubscribeButtonClick}>
<i className="fa fa-envelope for-small mr-1" /> {t('notificationsTab.subscribe')}
</Button>
<Button onClick={this.onUnSubscribeButtonClick}>
<i className="fa fa-times for-small mr-1" /> {t('notificationsTab.unsubscribe')}
</Button>
</ButtonGroup>
</Fragment>
)
}
}
export default translate(['tabsContent'], { wait: true })(PublishedEmailsTable)
@@ -0,0 +1,50 @@
import React from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'
import TableSwitcher from '../common/TableSwitcher/TableSwitcher'
import { NOTIFICATION_SUBSCREENS } from '../../../../../redux/modules/appState/share/tabs'
import { Button } from 'reactstrap'
class TopBar extends React.Component {
static propTypes = {
actions: PropTypes.object.isRequired,
tables: PropTypes.array.isRequired,
tableVisible: PropTypes.string.isRequired,
t: PropTypes.func.isRequired
}
onCreate = (type) => () => {
const { actions, tableVisible } = this.props
actions.startCreateNotification(type, tableVisible)
}
loadTable = (type) => {
this.props.actions.shareTables[type].loadTable(null)
}
render() {
const { t, tables, tableVisible, actions } = this.props
return (
<div className="notifications-topbar">
<div className="notifications-topbar_buttons_wrap">
<TableSwitcher
tables={tables}
tableVisible={tableVisible}
subTab="notifications"
switchTable={actions.switchShareTable}
loadTable={this.loadTable}
/>
<div className="notifications-buttons">
<Button className="btn-icon" color="primary" onClick={this.onCreate(NOTIFICATION_SUBSCREENS.ALERT_FORM)}>
<i className="lnr lnr-alarm btn-icon-wrapper"></i>
{t('notificationsTab.newAlert')}
</Button>
</div>
</div>
</div>
)
}
}
export default translate(['tabsContent'], { wait: true })(TopBar)
@@ -0,0 +1,399 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { translate } from 'react-i18next';
import {
timezones,
getCurrentTimezone
} from '../../../../../../common/Timezones';
import Select from 'react-select';
import moment from 'moment';
import DatePicker from 'react-datepicker';
import RecipientsSelect from './RecipientsSelect';
import CheckboxField from './CheckboxField';
import RadioField from './RadioField';
import BooleanRadioGroup from './BooleanRadioGroup';
import SourcesDropTarget from './sources/SourcesDropTarget';
import Sources from './sources/Sources';
import Scheduling from './scheduling/Scheduling';
import SaveAsPopup from './SaveAsPopup';
import History from './History';
import { EXTRAS } from '../../../../../../redux/modules/appState/share/forms/alertForm';
import { THEME_TYPES } from '../../../../../../redux/modules/appState/share/forms/notificationForm';
import {
Button,
Card,
CardBody,
CardTitle,
Col,
Container,
CustomInput,
Form,
FormGroup,
Input,
Label
} from 'reactstrap';
export class AlertForm extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
state: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired,
switchShareSubScreen: PropTypes.func.isRequired
};
changeName = (event) => {
this.props.actions.changeName(event.target.value);
};
changeSubject = (event) => {
this.props.actions.changeSubject(event.target.value);
};
changeAutoSubject = () => {
const { state, actions } = this.props;
actions.changeAutoSubject(!state.automatedSubject);
};
changeRecipient = (value) => {
this.props.actions.changeRecipients(value.split(','));
};
changeTimezone = (zone) => {
this.props.actions.changeTimezone(zone.value);
};
toggleTimezone = () => {
const { state, actions } = this.props;
if (state.isEnabledTimezone) {
actions.changeTimezone(getCurrentTimezone());
}
actions.toggleTimezone();
};
changeSendUntil = (value) => {
const sendUntil = value ? moment(value).format('YYYY-MM-DD') : '';
this.props.actions.changeSendUntil(sendUntil);
};
cancel = () => {
this.props.switchShareSubScreen('notifications', 'tables');
};
create = () => {
const { state, actions } = this.props;
const isEdit = !!state.id;
actions.saveAlert(isEdit);
};
edit = (name) => {
const { actions } = this.props;
actions.changeName(name);
actions.saveAlert(false);
};
showSaveAsPopup = () => {
this.props.actions.toggleSaveAsPopup();
};
render() {
const { state, actions, t } = this.props;
const isEdit = !!state.id;
const name = state.name;
const extract = state.content.extract;
const userComments = state.content.showInfo.userComments;
const sendUntil = !state.sendUntil
? state.sendUntil
: moment(state.sendUntil).toDate();
return (
<Card className="main-card mb-3">
<CardBody>
<CardTitle>
{isEdit
? t('notificationsTab.form.editAlert')
: t('notificationsTab.form.createAlert')}
</CardTitle>
<div className="share-tab-form-container">
<Container>
<Form>
<FormGroup row>
<Label sm={2}>{t('notificationsTab.form.name')}</Label>
<Col sm={10}>
<Input
type="text"
title={t('notificationsTab.form.nameTooltip')}
value={name}
onChange={this.changeName}
/>
</Col>
</FormGroup>
<RecipientsSelect t={t} state={state} actions={actions} />
<CheckboxField
label={t('notificationsTab.form.automatedEmail')}
additionalLabel={t(
'notificationsTab.form.automatedEmailDesc'
)}
value={state.automatedSubject}
onChange={this.changeAutoSubject}
/>
{!state.automatedSubject && (
<FormGroup row>
<Label sm={2}>
{t('notificationsTab.form.emailSubject')}
</Label>
<Col sm={10}>
<Input
type="text"
title={t('notificationsTab.form.emailSubject')}
value={state.subject}
onChange={this.changeSubject}
/>
</Col>
</FormGroup>
)}
<CheckboxField
label={t('notificationsTab.form.publish')}
additionalLabel={t('notificationsTab.form.publishDesc')}
value={state.published}
onChange={actions.changePublished}
/>
<CheckboxField
label={t('notificationsTab.form.unsubscribe')}
additionalLabel={t('notificationsTab.form.unsubscribeDesc')}
value={state.allowUnsubscribe}
onChange={actions.changeAllowUnsubscribe}
/>
<CheckboxField
label={t('notificationsTab.form.notifications')}
additionalLabel={t('notificationsTab.form.notificationsDesc')}
value={state.unsubscribeNotification}
onChange={actions.changeUnsubscribeNotification}
/>
<FormGroup row>
<Label sm={2}>{t('notificationsTab.form.feeds')}</Label>
<Col sm={10}>
<Sources
sources={state.sources}
removeSource={actions.removeSource}
moveSource={actions.moveSource}
/>
<SourcesDropTarget addSource={actions.addSource} />
</Col>
</FormGroup>
<FormGroup row>
<Label sm={2}>{t('notificationsTab.form.options')}</Label>
<Col sm={10}>
<FormGroup row>
<Col sm={2}>
{t('notificationsTab.form.articleExtracts')}
</Col>
<Col sm={10}>
<div className="d-flex">
<RadioField
label={t(
'notificationsTab.form.contextualExtracts'
)}
name="articleExtracts"
checkedValue={extract}
value={EXTRAS.CONTEXTUAL}
onChange={actions.changeExtras}
/>
<RadioField
label={t('notificationsTab.form.startExtracts')}
name="articleExtracts"
checkedValue={extract}
value={EXTRAS.START}
onChange={actions.changeExtras}
/>
<RadioField
label={t('notificationsTab.form.noExtracts')}
name="articleExtracts"
checkedValue={extract}
value={EXTRAS.NO}
onChange={actions.changeExtras}
/>
</div>
</Col>
</FormGroup>
<BooleanRadioGroup
mainLabel={t('notificationsTab.form.highlightKeywords')}
name="highlightKeywords"
value={state.content.highlightKeywords.highlight}
onChange={actions.changeHighlightKeywords}
/>
<BooleanRadioGroup
mainLabel={t('notificationsTab.form.showSourceCountry')}
name="showSourceCountry"
value={state.content.showInfo.sourceCountry}
onChange={actions.changeShowSourceCountry}
/>
<FormGroup row>
<Col sm={2}>
{t('notificationsTab.form.showUserComments')}
</Col>
<Col sm={10}>
<div className="d-flex">
<RadioField
label={t('common:commonWords.Yes')}
name="showUserComments"
checkedValue={userComments}
value="with_author_date"
onChange={actions.changeShowUserComments}
/>
<RadioField
label={t('common:commonWords.No')}
name="showUserComments"
checkedValue={userComments}
value="no"
onChange={actions.changeShowUserComments}
/>
</div>
</Col>
</FormGroup>
<FormGroup row>
<Col sm={2}>{t('notificationsTab.form.layout')}</Col>
<Col sm={10}>
<div className="d-flex">
<RadioField
label={t('notificationsTab.form.enhancedHtml')}
name="themeType"
checkedValue={state.themeType}
value={THEME_TYPES.ENHANCED}
onChange={actions.changeThemeType}
/>
<RadioField
label={t('notificationsTab.form.plainHtml')}
name="themeType"
checkedValue={state.themeType}
value={THEME_TYPES.PLAIN}
onChange={actions.changeThemeType}
/>
</div>
</Col>
</FormGroup>
<BooleanRadioGroup
mainLabel={t('notificationsTab.form.sendWhenEmpty')}
name="sendWhenEmpty"
value={state.sendWhenEmpty}
onChange={actions.changeSendWhenEmpty}
/>
</Col>
</FormGroup>
<FormGroup row>
<Label sm={2}>{t('notificationsTab.form.timezone')}</Label>
<Col sm={10}>
<Select
value={state.timezone}
options={timezones}
clearable={false}
disabled={!state.isEnabledTimezone}
onChange={this.changeTimezone}
/>
<CustomInput
id="toggleTimezone"
type="checkbox"
className="mt-1"
checked={state.isEnabledTimezone}
onChange={this.toggleTimezone}
label={t('notificationsTab.form.change')}
/>
</Col>
</FormGroup>
<FormGroup row>
<Label sm={2}>{t('notificationsTab.form.automatic')}</Label>
<Col sm={10}>
<Scheduling state={state.scheduling} actions={actions} />
</Col>
</FormGroup>
<FormGroup row>
<Label sm={2}>{t('notificationsTab.form.sendUntil')}</Label>
<Col sm={4}>
<DatePicker
className="form-control"
wrapperClassName="position-relative z-index-0"
dateFormat="yyyy-MM-dd"
placeholderText={t('notificationsTab.form.selectDate')}
selected={sendUntil}
minDate={moment()}
onChange={this.changeSendUntil}
/>
</Col>
</FormGroup>
{isEdit && (
<Fragment>
<hr />
<History
notificationId={state.id}
state={state.sendHistory}
actions={actions}
/>
</Fragment>
)}
<div className="text-right mb-3">
<Button
className="btn-icon"
color="secondary"
onClick={this.cancel}
>
<i className="lnr lnr-cross btn-icon-wrapper" />{' '}
{t('notificationsTab.form.cancel')}
</Button>
<Button
className="btn-icon ml-2"
color="success"
onClick={this.create}
>
<i className="lnr lnr-checkmark-circle btn-icon-wrapper" />
{t('notificationsTab.form.save')}
</Button>
<Button
className="btn-icon ml-2"
color="success"
onClick={this.showSaveAsPopup}
>
<i className="lnr lnr-checkmark-circle btn-icon-wrapper" />
{t('notificationsTab.form.saveAs')}
</Button>
</div>
</Form>
</Container>
{state.showSaveAsPopup && (
<SaveAsPopup
name={name}
togglePopup={actions.toggleSaveAsPopup}
onSubmit={this.edit}
/>
)}
</div>
</CardBody>
</Card>
);
}
}
export default translate(['tabsContent'], { wait: true })(AlertForm);
@@ -0,0 +1,64 @@
import React from 'react'
import PropTypes from 'prop-types'
import RadioField from './RadioField'
import { Col, FormGroup } from 'reactstrap'
import { translate } from 'react-i18next'
export class BooleanRadioGroup extends React.PureComponent {
static propTypes = {
mainLabel: PropTypes.string.isRequired,
trueLabel: PropTypes.string,
falseLabel: PropTypes.string,
name: PropTypes.string.isRequired,
value: PropTypes.bool,
onChange: PropTypes.func.isRequired
}
onChange = (event) => {
const { onChange } = this.props
let value = event.target.value
if (value === 'true' || value === 'false') {
value = value === 'true'
}
onChange(value)
}
render() {
const {
trueLabel = this.props.t('commonWords.Yes'),
falseLabel = this.props.t('commonWords.No'),
mainLabel,
name,
value,
onChange
} = this.props
return (
<FormGroup row>
<Col sm={2}>{mainLabel}</Col>
<Col sm={10}>
<div className="d-flex">
<RadioField
label={trueLabel}
name={name}
checkedValue={value}
value
onChange={onChange}
/>
<RadioField
label={falseLabel}
name={name}
checkedValue={value}
value={false}
onChange={onChange}
/>
</div>
</Col>
</FormGroup>
)
}
}
export default translate(['common'], { wait: true })(BooleanRadioGroup)
@@ -0,0 +1,39 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Col, CustomInput, FormGroup, Label } from 'reactstrap'
export class CheckboxField extends React.PureComponent {
static propTypes = {
label: PropTypes.string.isRequired,
additionalLabel: PropTypes.string.isRequired,
value: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired
};
onChange = () => {
const { onChange, value } = this.props
onChange(!value)
};
render () {
const { label, additionalLabel, value } = this.props
return (
<FormGroup row>
<Label sm={2}>{label}</Label>
<Col sm={10}>
<CustomInput
id={label}
type="checkbox"
checked={value}
onChange={this.onChange}
label={additionalLabel}
/>
</Col>
</FormGroup>
)
}
}
export default CheckboxField
@@ -0,0 +1,82 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { Button, Collapse, ListGroup, ListGroupItem } from 'reactstrap'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faSpinner } from '@fortawesome/free-solid-svg-icons'
import { translate } from 'react-i18next'
import { convertUTCtoLocal } from '../../../../../../common/helper'
export class History extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
notificationId: PropTypes.number.isRequired,
state: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired
}
onToggle = () => {
const { state, actions } = this.props
if (!state.isOpen && !state.isLoadingCompleted) {
this.showMore()
}
actions.toggleHistory()
}
showMore = () => {
const { notificationId, state, actions } = this.props
actions.getHistory(notificationId, state.page + 1, state.limit)
}
render() {
const { state, t } = this.props
const isOpen = state.isOpen
const label = isOpen ? t('notificationsTab.history.hideSendHistory') : t('notificationsTab.history.showSendHistory')
const iconClasses = classnames('lnr mr-2', {
'lnr-chevron-right': !isOpen,
'lnr-chevron-down': isOpen
})
return (
<div className="history">
<Button
color="link"
className="p-0 font-size-md"
onClick={this.onToggle}
>
<i className={iconClasses} />
{label}
</Button>
<Collapse isOpen={isOpen}>
{state.isLoadingCompleted && (
<div className="mt-3 ml-4">
<p className="text-muted mb-1">{t('notificationsTab.history.sentTime')}</p>
<ListGroup>
{state.entities.map((entity, i) => (
<ListGroupItem
className="col-sm-6 p-2"
key={`history-date-${i}`}
>
{convertUTCtoLocal(entity.date, 'DD MMM YYYY HH:mm')}
</ListGroupItem>
))}
</ListGroup>
{!state.isPending && state.entities.length < state.totalCount && (
<Button color="link" onClick={this.showMore}>
{t('notificationsTab.history.showMore')}
</Button>
)}
</div>
)}
{state.isPending && (
<p className="ml-4 mt-3">
<FontAwesomeIcon icon={faSpinner} className="mr-2" pulse /> {t('notificationsTab.history.loading')}
</p>
)}
</Collapse>
</div>
)
}
}
export default translate(['tabsContent'], { wait: true })(History)
@@ -0,0 +1,39 @@
import React from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'
import { Card, CardBody, CardTitle, Col, Form, FormGroup, Input, Label } from 'reactstrap'
export class NewsletterForm extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
state: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired
};
changeName = (event) => {
this.props.actions.changeName(event.target.value)
};
render () {
const { state, t } = this.props
return (
<Card className="main-card mb-3">
<CardBody>
<CardTitle>{t('notificationsTab.newsLetter.createNewsletter')}</CardTitle>
<Form>
<FormGroup row>
<Label sm={2}>{t('notificationsTab.newsLetter.name')}</Label>
<Col sm={10}>
<Input type="text" value={state.name} onChange={this.changeName} />
</Col>
</FormGroup>
</Form>
</CardBody>
</Card>
)
}
}
export default translate(['tabsContent'], { wait: true })(NewsletterForm)
@@ -0,0 +1,49 @@
import React from 'react'
import PropTypes from 'prop-types'
import { CustomInput } from 'reactstrap'
export class RadioField extends React.PureComponent {
static propTypes = {
label: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
checkedValue: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool
]),
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool
]),
onChange: PropTypes.func.isRequired
};
onChange = (event) => {
const { onChange } = this.props
let value = event.target.value
if (value === 'true' || value === 'false') {
value = value === 'true'
}
onChange(value)
};
render () {
const { label, name, checkedValue, value } = this.props
return (
<CustomInput
id={`${name}_${value}`}
type="radio"
className="mr-2"
name={name}
value={value}
checked={checkedValue === value}
onChange={this.onChange}
label={label}
/>
)
}
}
export default RadioField
@@ -0,0 +1,45 @@
import React from 'react'
import PropTypes from 'prop-types'
import Select from 'react-select'
import { Col, FormGroup, Label } from 'reactstrap'
export class RecipientsSelect extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
state: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired
};
loadOptions = (input) => {
const { actions } = this.props
return actions.getRecipients(input)
};
changeRecipient = (value) => {
const { actions } = this.props
actions.changeRecipients(value)
};
render () {
const { state, t } = this.props
const recipients = state.recipients
return (
<FormGroup row>
<Label sm={2}>{t('notificationsTab.form.recipient')}</Label>
<Col sm={10}>
<Select.Async
name="recipient-select"
loadOptions={this.loadOptions}
multi
value={recipients}
onChange={this.changeRecipient}
/>
</Col>
</FormGroup>
)
}
}
export default RecipientsSelect
@@ -0,0 +1,76 @@
import React from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'
import {
Button,
Modal,
ModalHeader,
ModalBody,
ModalFooter,
Label,
FormGroup,
Input
} from 'reactstrap'
export class SaveAsPopup extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
togglePopup: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
t: PropTypes.func.isRequired
}
constructor(props) {
super(props)
this.state = {
name: `${props.name} (copy)`
}
}
hidePopup = () => {
this.props.togglePopup()
}
onSubmit = () => {
this.props.onSubmit(this.state.name)
this.hidePopup()
}
handleChange = (e) => {
const { name, value } = e.target
this.setState({ [name]: value })
}
render() {
const { t } = this.props
return (
<Modal isOpen toggle={this.hidePopup} backdrop="static">
<ModalHeader toggle={this.hidePopup}>
{t('notificationsTab.popup.saveAs')}
</ModalHeader>
<ModalBody>
<FormGroup>
<Label>{t('notificationsTab.popup.saveAsPlaceholder')}</Label>
<Input
type="text"
name="name"
value={this.state.name}
onChange={this.handleChange}
/>
</FormGroup>
</ModalBody>
<ModalFooter>
<Button color="light" onClick={this.hidePopup}>
{t('common:commonWords.Cancel')}
</Button>
<Button color="primary" onClick={this.onSubmit}>
{t('notificationsTab.popup.save')}
</Button>
</ModalFooter>
</Modal>
)
}
}
export default translate(['tabsContent', 'common'], { wait: true })(SaveAsPopup)
@@ -0,0 +1,137 @@
import React from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'
import ScheduleSelectField from './ScheduleSelectField'
import { IoIosCloseCircleOutline } from 'react-icons/io'
export class ScheduleOptions extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
id: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
type: PropTypes.string.isRequired,
item: PropTypes.object.isRequired,
constants: PropTypes.object.isRequired,
canDelete: PropTypes.bool,
onChange: PropTypes.func.isRequired,
onRemove: PropTypes.func
};
onChange = (field, value) => {
const { id, item, onChange } = this.props
const newItem = {
...item,
[field]: value
}
onChange(id, newItem)
};
onRemove = () => {
const { id, canDelete, onRemove } = this.props
if (canDelete && onRemove) {
onRemove(id)
}
};
render () {
const { t, id, type, item, canDelete = false, constants } = this.props
const showTime = (type === 'daily' && item.time === 'once') || (type !== 'daily')
return (
<div className="schedule-options">
{id !== 'new' &&
<div>{t(`notificationsTab.form.type.${type}`)}&nbsp;</div>
}
<p>Send</p>
{type === 'daily' &&
<div className="schedule-options__group">
<ScheduleSelectField
field='time'
items={constants.time}
value={item.time}
onChange={this.onChange}
/>
<ScheduleSelectField
field='days'
items={constants.days}
value={item.days}
onChange={this.onChange}
/>
</div>
}
{type === 'weekly' &&
<div className="schedule-options__group">
<ScheduleSelectField
field='period'
items={constants.period}
value={item.period}
onChange={this.onChange}
/>
<ScheduleSelectField
field='day'
items={constants.day}
value={item.day}
onChange={this.onChange}
/>
</div>
}
{type === 'monthly' &&
<div className="schedule-options__group">
<ScheduleSelectField
needTranslate={false}
field='monthDay'
items={constants.monthDay}
value={item.monthDay}
onChange={this.onChange}
/>
</div>
}
{(type === 'weekly' || type === 'monthly') &&
<div>of the month&nbsp;</div>
}
{showTime &&
<div className="schedule-options__group">
<span>at</span>
<ScheduleSelectField
needTranslate={false}
field='hour'
items={constants.hour}
value={item.hour}
onChange={this.onChange}
/>
<span>:</span>
<ScheduleSelectField
needTranslate={false}
field='minute'
items={constants.minute}
value={item.minute}
onChange={this.onChange}
/>
</div>
}
{canDelete && (
<button
title="Remove"
type="button"
className="btn p-0"
onClick={this.onRemove}
>
<IoIosCloseCircleOutline size={22} className="text-danger ml-2" />
</button>
)}
</div>
)
}
}
export default translate(['tabsContent'], { wait: true })(ScheduleOptions)
@@ -0,0 +1,60 @@
import React from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'
import Select from 'react-select'
import classnames from 'classnames'
import { padLeft, addOrdinalSuffix } from '../../../../../../../common/StringUtils'
export class ScheduleSelectField extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
needTranslate: PropTypes.bool,
field: PropTypes.string.isRequired,
items: PropTypes.array.isRequired,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
onChange: PropTypes.func.isRequired
};
paddingFields = ['hour', 'minute'];
suffixFields = ['monthDay'];
onChange = (item) => {
const { field, onChange } = this.props
onChange(field, item.value)
};
render () {
const { t, needTranslate = true, items, value, field } = this.props
const classes = classnames('schedule-select-field', `schedule-select-field--${field}`)
const options = items.map(item => {
let label = ''
if (needTranslate) {
label = t(`notificationsTab.form.${field}.${item}`)
}
else {
label = (this.paddingFields.includes(field)) ? padLeft(item.toString(), 2) : item
label = (this.suffixFields.includes(field)) ? addOrdinalSuffix(item) : label
}
return {
value: item,
label
}
})
return (
<Select
className={classes}
options={options}
value={value}
clearable={false}
onChange={this.onChange}
/>
)
}
}
export default translate(['tabsContent'], { wait: true })(ScheduleSelectField)
@@ -0,0 +1,93 @@
import React, { Fragment } from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'
import TypeSelector from './TypeSelector'
import ScheduleOptions from './ScheduleOptions'
import { Button, ListGroup, ListGroupItem } from 'reactstrap'
export class Scheduling extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
state: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired
};
onChange = (id, item) => {
const { actions } = this.props
if (id === 'new') {
actions.changeNewSchedule(item)
} else {
actions.changeExistingSchedule(item, id)
}
};
addSchedule = () => {
this.props.actions.addSchedule()
};
removeSchedule = (id) => {
this.props.actions.removeSchedule(id)
};
render () {
const { state, actions, t } = this.props
const constants = state.constants
const activeType = state.newTime.type
return (
<Fragment>
<TypeSelector
types={constants.type}
activeType={activeType}
onChange={actions.changeScheduleType}
/>
<div className="new-schedule mb-3">
<ScheduleOptions
id="new"
type={activeType}
item={state.newTime}
constants={constants}
onChange={this.onChange}
/>
<Button
color="primary"
onClick={this.addSchedule}
>
{t('notificationsTab.form.add')}
</Button>
</div>
<div className="schedule-list">
<p className="text-muted mb-1">
{t('notificationsTab.form.activeScheduledTimes')} <span>({state.times.length})</span>
</p>
<ListGroup>
{state.times.map((time, i) => {
return (
<ListGroupItem
key={'schedule--added-time-' + i}
>
<ScheduleOptions
id={i}
type={time.type}
item={time}
canDelete
constants={constants}
onChange={this.onChange}
onRemove={this.removeSchedule}
/>
</ListGroupItem>
)
})}
</ListGroup>
</div>
</Fragment>
)
}
}
export default translate(['tabsContent'], { wait: true })(Scheduling)
@@ -0,0 +1,37 @@
import React from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'
import RadioField from '../RadioField'
export class TypeSelector extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
types: PropTypes.array.isRequired,
activeType: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
};
render () {
const { t, types, activeType, onChange } = this.props
return (
<div className="d-flex mb-2">
{types.map((type, i) => {
return (
<RadioField
key={'schedule-type-' + i}
label={t(`notificationsTab.form.type.${type}`)}
name="schedule-type"
checkedValue={activeType}
value={type}
onChange={onChange}
/>
)
})}
</div>
)
}
}
export default translate(['tabsContent'], { wait: true })(TypeSelector)
@@ -0,0 +1,76 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import {
IoIosArrowDropup,
IoIosArrowDropdown,
IoIosCloseCircleOutline
} from 'react-icons/io'
export class Source extends React.Component {
static propTypes = {
source: PropTypes.object.isRequired,
removeSource: PropTypes.func.isRequired,
moveSource: PropTypes.func.isRequired
}
onRemove = () => {
const { source, removeSource } = this.props
removeSource(source.id)
}
onMoveUp = () => {
const { source, moveSource } = this.props
moveSource(source.id, true)
}
onMoveDown = () => {
const { source, moveSource } = this.props
moveSource(source.id, false)
}
render() {
const { source } = this.props
return (
<div className="d-flex mr-2 mb-2">
<p
className={classnames(
'd-flex align-items-center feed-icon',
source.class
)}
>
{source.name}
</p>
<div className="ml-sm-4">
<button
title="Up"
type="button"
className="btn p-0"
onClick={this.onMoveUp}
>
<IoIosArrowDropup size={22} className="text-secondary ml-2" />
</button>
<button
title="Down"
type="button"
className="btn p-0"
onClick={this.onMoveDown}
>
<IoIosArrowDropdown size={22} className="text-secondary ml-2" />
</button>
<button
title="Remove"
type="button"
className="btn p-0"
onClick={this.onRemove}
>
<IoIosCloseCircleOutline size={22} className="text-danger ml-2" />
</button>
</div>
</div>
)
}
}
export default Source
@@ -0,0 +1,33 @@
import React from 'react'
import PropTypes from 'prop-types'
import Source from './Source'
export class Sources extends React.Component {
static propTypes = {
sources: PropTypes.array.isRequired,
removeSource: PropTypes.func.isRequired,
moveSource: PropTypes.func.isRequired
};
render () {
const { sources, removeSource, moveSource } = this.props
return (
<div>
{sources.map((source, i) => {
return (
<Source
key={'dragged-source-item-' + i}
source={source}
removeSource={removeSource}
moveSource={moveSource}
/>
)
})}
</div>
)
}
}
export default Sources
@@ -0,0 +1,45 @@
import React from 'react'
import PropTypes from 'prop-types'
import { compose } from 'redux'
import { DropTarget } from 'react-dnd'
import { translate } from 'react-i18next'
const target = {
drop (props, monitor) {
const item = monitor.getItem()
props.addSource(item.feed)
},
canDrop (props, monitor) {
return true
}
}
export class SourcesDropTargetClass extends React.Component {
static propTypes = {
t: PropTypes.func.isRequired,
connectDropTarget: PropTypes.func.isRequired,
addSource: PropTypes.func.isRequired
};
render () {
const { connectDropTarget, t } = this.props
return connectDropTarget(
<div className='dropzone-wrapper dropzone-wrapper-sm'>
<p className="dropzone-content">{t('notificationsTab.form.dragFeed')}</p>
</div>
)
}
}
export const SourcesDropTarget = compose(
DropTarget('feed', target, (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
itemType: monitor.getItemType()
})),
translate(['tabsContent'], { wait: true })
)(SourcesDropTargetClass)
export default SourcesDropTarget