Work in progress on migration to Bootstrap 4 and CoreUI admin theme

This commit is contained in:
Tomas Bures 2018-12-27 14:42:21 +01:00
parent 366bd09f2a
commit 3425e2c16a
85 changed files with 2093 additions and 10564 deletions

View file

@ -17,6 +17,7 @@ import axios
import {Button} from '../lib/bootstrap-components';
import {getUrl} from "../lib/urls";
import {withComponentMixins} from "../lib/decorator-helpers";
import styles from "./styles.scss"
@withComponentMixins([
withTranslation,
@ -66,33 +67,44 @@ export default class API extends Component {
}
return (
<div>
<div className={styles.api}>
<Title>{t('api')}</Title>
<div className="panel panel-default">
<div className="panel-body">
<div className="pull-right">
<Button label={this.state.accessToken ? t('resetAccessToken') : t('generateAccessToken')} icon="retweet" className="btn-info" onClickAsync={::this.resetAccessToken} />
<div className="card mb-3">
<div className="card-body">
<div className="float-right">
<Button label={this.state.accessToken ? t('resetAccessToken') : t('generateAccessToken')} icon="redo" className="btn-info" onClickAsync={::this.resetAccessToken} />
</div>
{accessTokenMsg}
</div>
</div>
<div className="well">
<h3>{t('notesAboutTheApi')}</h3>
<div className="card mb-3">
<div className="card-body">
<h4 className="card-title">{t('notesAboutTheApi')}</h4>
<ul>
<li>
<Trans i18nKey="apiResponseIsAJsonStructureWithErrorAnd">API response is a JSON structure with <code>error</code> and <code>data</code> properties. If the response <code>error</code> has a value set then the request failed.</Trans>
</li>
<li>
<Trans i18nKey="youNeedToDefineProperContentTypeWhen">You need to define proper <code>Content-Type</code> when making a request. You can either use <code>application/x-www-form-urlencoded</code> for normal form data or <code>application/json</code> for a JSON payload. Using <code>multipart/form-data</code> is not supported.</Trans>
</li>
</ul>
<ul className="card-text">
<li>
<Trans i18nKey="apiResponseIsAJsonStructureWithErrorAnd">API response is a JSON structure with <code>error</code> and <code>data</code> properties. If the response <code>error</code> has a value set then the request failed.</Trans>
</li>
<li>
<Trans i18nKey="youNeedToDefineProperContentTypeWhen">You need to define proper <code>Content-Type</code> when making a request. You can either use <code>application/x-www-form-urlencoded</code> for normal form data or <code>application/json</code> for a JSON payload. Using <code>multipart/form-data</code> is not supported.</Trans>
</li>
</ul>
</div>
</div>
<h3>POST /api/subscribe/:listId {t('addSubscription')}</h3>
<div className="card mb-3">
<div className="card-header">
<b>POST /api/subscribe/:listId {t('addSubscription')}</b>
</div>
<div className="card-body">
<p className="card-text">
{t('thisApiCallEitherInsertsANewSubscription')}
</p>
</div>
</div>
<h4>POST /api/subscribe/:listId {t('addSubscription')}</h4>
<p>
{t('thisApiCallEitherInsertsANewSubscription')}
@ -137,7 +149,7 @@ export default class API extends Component {
<pre>curl -XPOST '{getUrl(`api/subscribe/B16uVTdW?access_token=${accessToken}`)}' \
--data 'EMAIL=test@example.com&amp;MERGE_CHECKBOX=yes&amp;REQUIRE_CONFIRMATION=yes'</pre>
<h3>POST /api/unsubscribe/:listId {t('removeSubscription')}</h3>
<h4>POST /api/unsubscribe/:listId {t('removeSubscription')}</h4>
<p>
{t('thisApiCallMarksASubscriptionAs')}
@ -164,7 +176,7 @@ export default class API extends Component {
<pre>curl -XPOST '{getUrl(`api/unsubscribe/B16uVTdW?access_token=${accessToken}`)}' \
--data 'EMAIL=test@example.com'</pre>
<h3>POST /api/delete/:listId {t('deleteSubscription')}</h3>
<h4>POST /api/delete/:listId {t('deleteSubscription')}</h4>
<p>
{t('thisApiCallDeletesASubscription')}
@ -191,7 +203,7 @@ export default class API extends Component {
<pre>curl -XPOST '{getUrl(`api/delete/B16uVTdW?access_token=${accessToken}`)}' \
--data 'EMAIL=test@example.com'</pre>
<h3>POST /api/field/:listId {t('addNewCustomField')}</h3>
<h4>POST /api/field/:listId {t('addNewCustomField')}</h4>
<p>
{t('thisApiCallCreatesANewCustomFieldForA')}
@ -239,7 +251,7 @@ export default class API extends Component {
<pre>curl -XPOST '{getUrl(`api/field/B16uVTdW?access_token=${accessToken}`)}' \
--data 'NAME=Birthday&amp;TYPE=birthday-us&amp;VISIBLE=yes'</pre>
<h3>GET /api/blacklist/get {t('getListOfBlacklistedEmails')}</h3>
<h4>GET /api/blacklist/get {t('getListOfBlacklistedEmails')}</h4>
<p>
{t('thisApiCallGetListOfBlacklistedEmails')}
@ -264,7 +276,7 @@ export default class API extends Component {
<pre>curl -XGET '{getUrl(`api/blacklist/get?access_token=${accessToken}&limit=10&start=10&search=gmail`)}' </pre>
<h3>POST /api/blacklist/add {t('addEmailToBlacklist')}</h3>
<h4>POST /api/blacklist/add {t('addEmailToBlacklist')}</h4>
<p>
{t('thisApiCallEitherAddEmailsToBlacklist')}
@ -291,7 +303,7 @@ export default class API extends Component {
<pre>curl -XPOST '{getUrl(`api/blacklist/add?access_token={accessToken}`)}' \
--data 'EMAIL=test@example.com&amp;'</pre>
<h3>POST /api/blacklist/delete {t('deleteEmailFromBlacklist')}</h3>
<h4>POST /api/blacklist/delete {t('deleteEmailFromBlacklist')}</h4>
<p>
{t('thisApiCallEitherDeleteEmailsFrom')}
@ -318,7 +330,7 @@ export default class API extends Component {
<pre>curl -XPOST '{getUrl(`api/blacklist/delete?access_token=${accessToken}`)}' \
--data 'EMAIL=test@example.com&amp;'</pre>
<h3>GET /api/lists/:email {t('getTheListsAUserHasSubscribedTo')}</h3>
<h4>GET /api/lists/:email {t('getTheListsAUserHasSubscribedTo')}</h4>
<p>
{t('retrieveTheListsThatTheUserWithEmailHas')}

View file

@ -207,7 +207,7 @@ export default class Account extends Component {
</Fieldset>
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('update')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('update')}/>
</ButtonRow>
</Form>
</div>

View file

@ -76,7 +76,7 @@ export default class Forget extends Component {
<InputField id="usernameOrEmail" label={t('usernameOrEmail')}/>
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('sendEmail')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('sendEmail')}/>
</ButtonRow>
</Form>
</div>

View file

@ -121,7 +121,7 @@ export default class Login extends Component {
<CheckBox id="remember" text={t('rememberMe')}/>
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('signIn')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('signIn')}/>
{passwordResetLink}
</ButtonRow>
</Form>

View file

@ -161,7 +161,7 @@ export default class Account extends Component {
<InputField id="password2" label={t('confirmPassword')} type="password"/>
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('resetPassword')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('resetPassword')}/>
</ButtonRow>
</Form>
</div>

View file

@ -0,0 +1,9 @@
.api {
:global .card h4 {
margin-top: 0px;
}
h4 {
margin-top: 45px;
}
}

View file

@ -131,7 +131,7 @@ export default class List extends Component {
<InputField id="email" label={t('email')}/>
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('addToBlacklist')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('addToBlacklist')}/>
</ButtonRow>
</Form>

View file

@ -507,21 +507,21 @@ export default class CUD extends Component {
<div className={campaignsStyles.entryButtons}>
{lsts.length > 1 &&
<Button
className="btn-default"
className="btn-secondary"
icon="remove"
title={t('remove')}
onClickAsync={() => this.onRemoveListEntry(lstUid)}
/>
}
<Button
className="btn-default"
className="btn-secondary"
icon="plus"
title={t('insertNewEntryBeforeThisOne')}
onClickAsync={() => this.onAddListEntry(lstOrderIdxClosure)}
/>
{lstOrderIdx > 0 &&
<Button
className="btn-default"
className="btn-secondary"
icon="chevron-up"
title={t('moveUp')}
onClickAsync={() => this.onListEntryMoveUp(lstOrderIdxClosure)}
@ -529,7 +529,7 @@ export default class CUD extends Component {
}
{lstOrderIdx < lsts.length - 1 &&
<Button
className="btn-default"
className="btn-secondary"
icon="chevron-down"
title={t('moveDown')}
onClickAsync={() => this.onListEntryMoveDown(lstOrderIdxClosure)}
@ -559,7 +559,7 @@ export default class CUD extends Component {
{lstsEditEntries}
<div key="newEntry" className={campaignsStyles.newEntry}>
<Button
className="btn-default"
className="btn-secondary"
icon="plus"
label={t('addList')}
onClickAsync={() => this.onAddListEntry(lsts.length)}
@ -737,7 +737,7 @@ export default class CUD extends Component {
{templateEdit}
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={saveButtonLabel}/>
<Button type="submit" className="btn-primary" icon="check" label={saveButtonLabel}/>
{canDelete && <NavButton className="btn-danger" icon="remove" label={t('delete')} linkTo={`/campaigns/${this.props.entity.id}/delete`}/> }
</ButtonRow>
</Form>

View file

@ -228,7 +228,7 @@ export default class CustomContent extends Component {
{customTemplateTypeKey && getEditForm(this, customTemplateTypeKey, 'data_sourceCustom_')}
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('save')}/>
<Button className="btn-danger" icon="send" label={t('testSend')} onClickAsync={async () => this.setState({showTestSendModal: true})}/>
</ButtonRow>
</Form>

View file

@ -308,7 +308,7 @@ class SendControls extends Component {
:
<Button className="btn-primary" icon="send" label={t('send') + subscrInfo} onClickAsync={::this.startAsync}/>
}
{entity.status === CampaignStatus.PAUSED && <NavButton className="btn-default" icon="signal" label={t('viewStatistics')} linkTo={`/campaigns/${entity.id}/statistics`}/>}
{entity.status === CampaignStatus.PAUSED && <NavButton className="btn-secondary" icon="signal" label={t('viewStatistics')} linkTo={`/campaigns/${entity.id}/statistics`}/>}
</ButtonRow>
</div>
);
@ -321,7 +321,7 @@ class SendControls extends Component {
</AlignedRow>
<ButtonRow>
<Button className="btn-primary" icon="stop" label={t('stop')} onClickAsync={::this.stopAsync}/>
<NavButton className="btn-default" icon="signal" label={t('viewStatistics')} linkTo={`/campaigns/${entity.id}/statistics`}/>
<NavButton className="btn-secondary" icon="signal" label={t('viewStatistics')} linkTo={`/campaigns/${entity.id}/statistics`}/>
</ButtonRow>
</div>
);
@ -337,7 +337,7 @@ class SendControls extends Component {
<ButtonRow>
<Button className="btn-primary" icon="play" label={t('continue') + subscrInfo} onClickAsync={::this.startAsync}/>
<Button className="btn-primary" icon="refresh" label={t('reset')} onClickAsync={::this.resetAsync}/>
<NavButton className="btn-default" icon="signal" label={t('viewStatistics')} linkTo={`/campaigns/${entity.id}/statistics`}/>
<NavButton className="btn-secondary" icon="signal" label={t('viewStatistics')} linkTo={`/campaigns/${entity.id}/statistics`}/>
</ButtonRow>
</div>
);

View file

@ -235,7 +235,7 @@ export default class CUD extends Component {
<CheckBox id="enabled" text={t('enabled')}/>
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('save')}/>
{isEdit && <NavButton className="btn-danger" icon="remove" label={t('delete')} linkTo={`/campaigns/${this.props.campaign.id}/triggers/${this.props.entity.id}/delete`}/>}
</ButtonRow>
</Form>

View file

@ -10,11 +10,11 @@ import {
} from './error-handling';
import {withComponentMixins} from "./decorator-helpers";
@withComponentMixins([
withTranslation,
withErrorHandling
])
class DismissibleAlert extends Component {
static propTypes = {
severity: PropTypes.string.isRequired,
@ -49,24 +49,31 @@ class Icon extends Component {
}
static defaultProps = {
family: 'glyphicon'
family: 'fas'
}
render() {
const props = this.props;
return <span className={`${props.family} ${props.family}-${props.icon}` + (props.className ? ' ' + props.className : '')} title={props.title}></span>;
if (props.family === 'fas' || props.family === 'far') {
return <i className={`${props.family} fa-${props.icon} ${props.className || ''}`} title={props.title}></i>;
} else {
console.error(`Icon font family ${props.family} not supported. (icon: ${props.icon}, title: ${props.title})`)
return null;
}
}
}
@withComponentMixins([
withErrorHandling
])
class Button extends Component {
static propTypes = {
onClickAsync: PropTypes.func,
label: PropTypes.string,
icon: PropTypes.string,
iconTitle: PropTypes.string,
className: PropTypes.string,
type: PropTypes.string
}
@ -91,7 +98,7 @@ class Button extends Component {
let icon;
if (props.icon) {
icon = <Icon icon={props.icon}/>
icon = <Icon icon={props.icon} title={props.iconTitle}/>
}
let iconSpacer;
@ -105,6 +112,7 @@ class Button extends Component {
}
}
//TODO
class DropdownMenu extends Component {
static propTypes = {
label: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
@ -141,6 +149,7 @@ class DropdownMenu extends Component {
}
}
//TODO
class DropdownMenuItem extends Component {
static propTypes = {
label: PropTypes.string,
@ -151,7 +160,7 @@ class DropdownMenuItem extends Component {
render() {
const props = this.props;
let className = 'dropdown';
let className = 'nav-item dropdown';
if (props.className) {
className = className + ' ' + props.className;
}
@ -159,12 +168,12 @@ class DropdownMenuItem extends Component {
return (
<li className={className}>
{props.icon ?
<a href="#" className="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
<Icon icon={props.icon}/>{' '}{props.label}{' '}<span className="caret"></span>
<a href="#" className="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
<Icon icon={props.icon}/>{' '}{props.label}
</a>
:
<a href="#" className="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
{props.label}{' '}<span className="caret"></span>
<a href="#" className="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
{props.label}
</a>
}
<ul className="dropdown-menu">
@ -208,6 +217,7 @@ class ActionLink extends Component {
withTranslation,
withErrorHandling
])
//TODO
class ModalDialog extends Component {
constructor(props) {
super(props);
@ -215,7 +225,7 @@ class ModalDialog extends Component {
const t = props.t;
this.state = {
buttons: this.props.buttons || [ { label: t('close'), className: 'btn-default', onClickAsync: null } ]
buttons: this.props.buttons || [ { label: t('close'), className: 'btn-secondary', onClickAsync: null } ]
};
}

View file

@ -109,7 +109,7 @@ class Form extends Component {
const statusMessageText = owner.getFormStatusMessageText();
const statusMessageSeverity = owner.getFormStatusMessageSeverity();
let formClass = `form-horizontal ${styles.form} `;
let formClass = styles.form;
if (props.format === 'wide') {
formClass = '';
} else if (props.format === 'inline') {
@ -166,14 +166,14 @@ class Fieldset extends Component {
let helpBlock = null;
if (this.props.help) {
helpBlock = <div className="help-block" id={htmlId + '_help'}>{this.props.help}</div>;
helpBlock = <small className="form-text text-muted" id={htmlId + '_help'}>{this.props.help}</small>;
}
let validationBlock = null;
if (id) {
const validationMsg = id && owner.getFormValidationMessage(id);
if (validationMsg) {
validationBlock = <div className="help-block" id={htmlId + '_help_validation'}>{validationMsg}</div>;
validationBlock = <small className="form-text text-muted" id={htmlId + '_help_validation'}>{validationMsg}</small>;
}
}
@ -194,29 +194,22 @@ function wrapInput(id, htmlId, owner, format, rightContainerClass, label, help,
// wrapInput may be used also outside forms to make a kind of fake read-only forms
let className;
if (owner) {
if (id) {
className = owner.addFormValidationClass('form-group', id);
} else {
className = 'form-group';
}
className = 'form-group row';
} else {
className = 'row ' + styles.staticFormGroup;
}
let colLeft = '';
let colRight = '';
let offsetRight = '';
switch (format) {
case 'wide':
colLeft = '';
colRight = '';
offsetRight = '';
break;
default:
colLeft = 'col-sm-2';
colLeft = 'col-sm-2 col-form-label';
colRight = 'col-sm-10';
offsetRight = 'col-sm-offset-2';
break;
}
@ -225,20 +218,22 @@ function wrapInput(id, htmlId, owner, format, rightContainerClass, label, help,
let helpBlock = null;
if (help) {
helpBlock = <div className={`help-block ${colRight} ${offsetRight}`} id={htmlId + '_help'}>{help}</div>;
helpBlock = <small className={`form-text text-muted`} id={htmlId + '_help'}>{help}</small>;
}
let validationBlock = null;
if (id) {
const validationMsg = id && owner.getFormValidationMessage(id);
if (validationMsg) {
validationBlock = <div className={`help-block ${colRight} ${offsetRight}`} id={htmlId + '_help_validation'}>{validationMsg}</div>;
validationBlock = <div className="invalid-feedback" id={htmlId + '_help_validation'}>{validationMsg}</div>;
}
}
let labelBlock = null;
if (label) {
labelBlock = <label htmlFor={htmlId} className="control-label">{label}</label>;
labelBlock = <label className={colLeft}>{label}</label>;
} else {
labelBlock = <div className={colLeft}/>
}
if (format === 'inline') {
@ -252,14 +247,12 @@ function wrapInput(id, htmlId, owner, format, rightContainerClass, label, help,
} else {
return (
<div className={className} >
<div className={colLeft}>
{labelBlock}
</div>
{labelBlock}
<div className={`${colRight} ${rightContainerClass}`}>
{input}
{helpBlock}
{validationBlock}
</div>
{helpBlock}
{validationBlock}
</div>
);
}
@ -325,8 +318,10 @@ class InputField extends Component {
type = 'hidden';
}
const className = owner.addFormValidationClass('form-control', id);
return wrapInput(id, htmlId, owner, props.format, '', props.label, props.help,
<input type={type} value={owner.getFormValue(id)} placeholder={props.placeholder} id={htmlId} className="form-control" aria-describedby={htmlId + '_help'} onChange={evt => owner.updateFormValue(id, evt.target.value)}/>
<input type={type} value={owner.getFormValue(id)} placeholder={props.placeholder} id={htmlId} className={className} aria-describedby={htmlId + '_help'} onChange={evt => owner.updateFormValue(id, evt.target.value)}/>
);
}
}
@ -349,11 +344,13 @@ class CheckBox extends Component {
const id = this.props.id;
const htmlId = 'form_' + id;
return wrapInput(id, htmlId, owner, props.format, 'checkbox', props.label, props.help,
<label>
<input type="checkbox" checked={owner.getFormValue(id)} id={htmlId} aria-describedby={htmlId + '_help'} onChange={evt => owner.updateFormValue(id, !owner.getFormValue(id))}/>
{props.text}
</label>
const className = owner.addFormValidationClass('form-check-input', id);
return wrapInput(id, htmlId, owner, props.format, '', props.label, props.help,
<div className="form-group form-check my-2">
<input className={className} type="checkbox" checked={owner.getFormValue(id)} id={htmlId} aria-describedby={htmlId + '_help'} onChange={evt => owner.updateFormValue(id, !owner.getFormValue(id))}/>
<label className="form-check-label" htmlFor={htmlId}>{props.text}</label>
</div>
);
}
}
@ -393,12 +390,13 @@ class CheckBoxGroup extends Component {
const options = [];
for (const option of props.options) {
options.push(
<div key={option.key} className="checkbox">
<label>
<input type="checkbox" checked={selection.includes(option.key)} onChange={evt => this.onChange(option.key)}/>
{option.label}
</label>
const optClassName = owner.addFormValidationClass('form-check-input', id);
const optId = htmlId + '_' + option.key;
let number = options.push(
<div key={option.key} className="form-group form-check my-2">
<input id={optId} type="checkbox" className={optClassName} checked={selection.includes(option.key)} onChange={evt => this.onChange(option.key)}/>
<label className="form-check-label" htmlFor={optId}>{option.label}</label>
</div>
);
}
@ -440,12 +438,13 @@ class RadioGroup extends Component {
const options = [];
for (const option of props.options) {
options.push(
<div key={option.key} className="radio">
<label>
<input type="radio" name={htmlId} checked={value === option.key} onChange={evt => owner.updateFormValue(id, option.key)}/>
{option.label}
</label>
const optClassName = owner.addFormValidationClass('form-check-input', id);
const optId = htmlId + '_' + option.key;
let number = options.push(
<div key={option.key} className="form-group form-check my-2">
<input id={optId} type="radio" className={optClassName} name={htmlId} checked={value === option.key} onChange={evt => owner.updateFormValue(id, option.key)}/>
<label className="form-check-label" htmlFor={optId}>{option.label}</label>
</div>
);
}
@ -481,10 +480,10 @@ class TextArea extends Component {
const owner = this.getFormStateOwner();
const id = props.id;
const htmlId = 'form_' + id;
const className = props.className || ''
const className = owner.addFormValidationClass('form-control ' + (props.className || '') , id);
return wrapInput(id, htmlId, owner, props.format, '', props.label, props.help,
<textarea id={htmlId} placeholder={props.placeholder} value={owner.getFormValue(id) || ''} className={`form-control ${className}`} aria-describedby={htmlId + '_help'} onChange={evt => owner.updateFormValue(id, evt.target.value)}></textarea>
<textarea id={htmlId} placeholder={props.placeholder} value={owner.getFormValue(id) || ''} className={className} aria-describedby={htmlId + '_help'} onChange={evt => owner.updateFormValue(id, evt.target.value)}></textarea>
);
}
}
@ -518,7 +517,7 @@ class DatePicker extends Component {
dateFormat: DateFormat.INTL
}
toggleDayPicker() {
async toggleDayPicker() {
this.setState({
opened: !this.state.opened
});
@ -591,11 +590,15 @@ class DatePicker extends Component {
placeholder = getDateFormatString(props.dateFormat);
}
const className = owner.addFormValidationClass('form-control', id);
return wrapInput(id, htmlId, owner, props.format, '', props.label, props.help,
<div>
<div className="input-group">
<input type="text" value={selectedDateStr} placeholder={placeholder} id={htmlId} className="form-control" aria-describedby={htmlId + '_help'} onChange={evt => owner.updateFormValue(id, evt.target.value)}/>
<span className="input-group-addon" onClick={::this.toggleDayPicker}><Icon icon="calendar" title={t('openCalendar')}/></span>
<input type="text" value={selectedDateStr} placeholder={placeholder} id={htmlId} className={className} aria-describedby={htmlId + '_help'} onChange={evt => owner.updateFormValue(id, evt.target.value)}/>
<div className="input-group-append">
<Button iconTitle={t('openCalendar')} className="btn-secondary" icon="calendar-alt" onClickAsync={::this.toggleDayPicker}/>
</div>
</div>
{this.state.opened &&
<div className={styles.dayPickerWrapper}>
@ -650,10 +653,7 @@ class Dropdown extends Component {
}
}
let className = 'form-control';
if (props.className) {
className += ' ' + props.className;
}
const className = owner.addFormValidationClass('form-control ' + (props.className || '') , id);
return wrapInput(id, htmlId, owner, props.format, '', props.label, props.help,
<select id={htmlId} className={className} aria-describedby={htmlId + '_help'} value={owner.getFormValue(id)} onChange={evt => owner.updateFormValue(id, evt.target.value)}>
@ -730,8 +730,10 @@ class TreeTableSelect extends Component {
const id = this.props.id;
const htmlId = 'form_' + id;
const className = owner.addFormValidationClass('' , id);
return wrapInput(id, htmlId, owner, props.format, '', props.label, props.help,
<TreeTable data={props.data} dataUrl={props.dataUrl} selectMode={TreeSelectMode.SINGLE} selection={owner.getFormValue(id)} onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
<TreeTable className={className} data={props.data} dataUrl={props.dataUrl} selectMode={TreeSelectMode.SINGLE} selection={owner.getFormValue(id)} onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
);
}
}
@ -823,14 +825,16 @@ class TableSelect extends Component {
const t = props.t;
if (props.dropdown) {
const className = owner.addFormValidationClass('form-control' , id);
return wrapInput(id, htmlId, owner, props.format, '', props.label, props.help,
<div>
<div className={(props.disabled ? '' : 'input-group ') + styles.tableSelectDropdown}>
<input type="text" className="form-control" value={this.state.selectedLabel} onClick={::this.toggleOpen} readOnly={!props.disabled} disabled={props.disabled}/>
<input type="text" className={className} value={this.state.selectedLabel} onClick={::this.toggleOpen} readOnly={!props.disabled} disabled={props.disabled}/>
{!props.disabled &&
<span className="input-group-btn">
<Button label={t('select')} className="btn-default" onClickAsync={::this.toggleOpen}/>
</span>
<div className="input-group-append">
<Button label={t('select')} className="btn-secondary" onClickAsync={::this.toggleOpen}/>
</div>
}
</div>
<div className={styles.tableSelectTable + (this.state.open ? '' : ' ' + styles.tableSelectTableHidden)}>
@ -1243,9 +1247,9 @@ const withForm = createComponentMixin([], [], (TargetClass, InnerClass) => {
if (this.isFormValidationShown()) {
const error = this.getFormError(name);
if (error) {
return className + ' has-error';
return className + ' is-invalid';
} else {
return className + ' has-success';
return className + ' is-valid';
}
} else {
return className;

View file

@ -228,7 +228,7 @@ export function tableAddDeleteButton(actions, owner, perms, deleteUrl, name, del
});
} else {
actions.push({
label: <Icon icon="remove" title={t('delete')}/>,
label: <Icon icon="trash-alt" title={t('delete')}/>,
action: () => {
owner.tableRestActionDialogData = {
shown: true,

View file

@ -20,7 +20,8 @@ import interoperableErrors
from "../../../shared/interoperable-errors";
import {
Button,
DismissibleAlert
DismissibleAlert,
Icon
} from "./bootstrap-components";
import mailtrainConfig
from "mailtrainConfig";
@ -62,7 +63,7 @@ class Breadcrumb extends Component {
}
if (isActive) {
return <li key={entry.path} className="active">{title}</li>;
return <li key={entry.path} className="breadcrumb-item active">{title}</li>;
} else if (entry.externalLink) {
let externalLink;
@ -72,7 +73,7 @@ class Breadcrumb extends Component {
externalLink = entry.externalLink;
}
return <li key={entry.path}><a href={externalLink}>{title}</a></li>;
return <li key={entry.path} className="breadcrumb-item"><a href={externalLink}>{title}</a></li>;
} else if (entry.link) {
let link;
@ -81,10 +82,10 @@ class Breadcrumb extends Component {
} else {
link = entry.link;
}
return <li key={entry.path}><Link to={link}>{title}</Link></li>;
return <li key={entry.path} className="breadcrumb-item"><Link to={link}>{title}</Link></li>;
} else {
return <li key={entry.path}>{title}</li>;
return <li key={entry.path} className="breadcrumb-item">{title}</li>;
}
}
@ -93,10 +94,11 @@ class Breadcrumb extends Component {
const renderedElems = [...route.parents.map(x => this.renderElement(x)), this.renderElement(route, true)];
return <ol className="breadcrumb">{renderedElems}</ol>;
return <nav aria-label="breadcrumb"><ol className="breadcrumb">{renderedElems}</ol></nav>;
}
}
//TODO
class SecondaryNavBar extends Component {
static propTypes = {
route: PropTypes.object.isRequired,
@ -441,7 +443,7 @@ export class Toolbar extends Component {
};
render() {
let className = 'pull-right ' + styles.buttonRow;
let className = 'float-right ' + styles.buttonRow;
if (this.props.className) {
className += ' ' + this.props.className;
}
@ -471,6 +473,7 @@ export class NavButton extends Component {
}
}
// TODO
export class MenuLink extends Component {
static propTypes = {
to: PropTypes.string,
@ -480,12 +483,78 @@ export class MenuLink extends Component {
render() {
const props = this.props;
const clsName = "nav-item" + (props.className ? " " + props.className : "")
return (
<li className={props.className}><Link to={props.to}>{props.children}</Link></li>
<li className={clsName}><Link to={props.to} className="nav-link">{props.children}</Link></li>
);
}
}
export class NavLink extends Component {
static propTypes = {
to: PropTypes.string,
className: PropTypes.string
}
render() {
const props = this.props;
const clsName = "nav-item" + (props.className ? " " + props.className : "")
return (
<li className={clsName}><Link to={props.to} className="nav-link">{props.children}</Link></li>
);
}
}
export class NavDropdown extends Component {
static propTypes = {
label: PropTypes.string,
icon: PropTypes.string,
className: PropTypes.string,
menuClassName: PropTypes.string
}
render() {
const props = this.props;
const className = 'nav-item dropdown' + (props.className ? ' ' + props.className : '');
const menuClassName = 'dropdown-menu' + (props.menuClassName ? ' ' + props.menuClassName : '');
return (
<li className={className}>
{props.icon ?
<a href="#" className="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
<Icon icon={props.icon}/>{' '}{props.label}
</a>
:
<a href="#" className="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
{props.label}
</a>
}
<ul className={menuClassName}>
{props.children}
</ul>
</li>
);
}
}
export class NavDropdownLink extends Component {
static propTypes = {
to: PropTypes.string
}
render() {
const props = this.props;
return (
<Link to={props.to} className="dropdown-item">{props.children}</Link>
);
}
}
export const requiresAuthenticatedUser = createComponentMixin([], [withPageHelpers], (TargetClass, InnerClass) => {
class RequiresAuthenticatedUser extends React.Component {
constructor(props) {

View file

@ -29,7 +29,7 @@ $editorNormalHeight: 800px !default;
}
.navbar {
background: #DE4320;
background: #f86c6b;
width: 100%;
height: $navbarHeight;
}
@ -44,8 +44,6 @@ $editorNormalHeight: 800px !default;
.title {
padding: 5px 0 5px 10px;
font-size: 18px;
font-family: sans-serif;
font-family: "Ubuntu",Tahoma,"Helvetica Neue",Helvetica,Arial,sans-serif;
font-weight: bold;
float: left;
color: white;
@ -58,20 +56,29 @@ $editorNormalHeight: 800px !default;
padding: 0px 15px;
line-height: $navbarHeight;
text-align: center;
color: white;
font-size: 14px;
font-weight: bold;
font-family: sans-serif;
cursor: pointer;
&, &:not([href]):not([tabindex]) { // This is to override reboot.scss in bootstrap
color: white;
}
}
.btn:hover {
background-color: #b1381e;
color: white;
background-color: #c05454;
text-decoration: none;
&, &:not([href]):not([tabindex]) { // This is to override reboot.scss in bootstrap
color: white;
}
}
.btnDisabled {
color: #581c00;
cursor: default;
&, &:not([href]):not([tabindex]) { // This is to override reboot.scss in bootstrap
color: #621d1d;
}
}

View file

@ -96,7 +96,7 @@ export class CKEditorHost extends Component {
<div className={styles.navbar}>
{this.state.fullscreen && <img className={styles.logo} src={getTrustedUrl('static/mailtrain-notext.png')}/>}
<div className={styles.title}>{this.props.title}</div>
<a className={styles.btn} onClick={::this.toggleFullscreenAsync}><Icon icon="fullscreen"/></a>
<a className={styles.btn} onClick={::this.toggleFullscreenAsync}><Icon icon="window-maximize"/></a>
<a className={styles.btn} onClick={this.props.onTestSend}><Icon icon="send"/></a>
{this.props.canSave ? <a className={styles.btn} onClick={this.props.onSave}><Icon icon="floppy-disk"/></a> : <span className={styles.btnDisabled}><Icon icon="floppy-disk"/></span>}
</div>

View file

@ -90,7 +90,7 @@ export class CodeEditorHost extends Component {
<div className={styles.navbar}>
{this.state.fullscreen && <img className={styles.logo} src={getTrustedUrl('static/mailtrain-notext.png')}/>}
<div className={styles.title}>{this.props.title}</div>
<a className={styles.btn} onClick={::this.toggleFullscreenAsync}><Icon icon="fullscreen"/></a>
<a className={styles.btn} onClick={::this.toggleFullscreenAsync}><Icon icon="window-maximize"/></a>
<a className={styles.btn} onClick={this.props.onTestSend}><Icon icon="send"/></a>
<a className={styles.btn} onClick={::this.togglePreviewAsync}><Icon icon={this.state.preview ? 'eye-close': 'eye-open'}/></a>
{this.props.canSave ? <a className={styles.btn} onClick={this.props.onSave}><Icon icon="floppy-disk"/></a> : <span className={styles.btnDisabled}><Icon icon="floppy-disk"/></span>}

View file

@ -70,7 +70,7 @@ export class GrapesJSHost extends Component {
<div className={styles.navbar}>
{this.state.fullscreen && <img className={styles.logo} src={getTrustedUrl('static/mailtrain-notext.png')}/>}
<div className={styles.title}>{this.props.title}</div>
<a className={styles.btn} onClick={::this.toggleFullscreenAsync}><Icon icon="fullscreen"/></a>
<a className={styles.btn} onClick={::this.toggleFullscreenAsync}><Icon icon="window-maximize"/></a>
<a className={styles.btn} onClick={this.props.onTestSend}><Icon icon="send"/></a>
{this.props.canSave ? <a className={styles.btn} onClick={this.props.onSave}><Icon icon="floppy-disk"/></a> : <span className={styles.btnDisabled}><Icon icon="floppy-disk"/></span>}
</div>

View file

@ -73,7 +73,7 @@ export class MosaicoHost extends Component {
<div className={styles.navbar}>
{this.state.fullscreen && <img className={styles.logo} src={getTrustedUrl('static/mailtrain-notext.png')}/>}
<div className={styles.title}>{this.props.title}</div>
<a className={styles.btn} onClick={::this.toggleFullscreenAsync}><Icon icon="fullscreen"/></a>
<a className={styles.btn} onClick={::this.toggleFullscreenAsync}><Icon icon="window-maximize"/></a>
<a className={styles.btn} onClick={this.props.onTestSend}><Icon icon="send"/></a>
{this.props.canSave ? <a className={styles.btn} onClick={this.props.onSave}><Icon icon="floppy-disk"/></a> : <span className={styles.btnDisabled}><Icon icon="floppy-disk"/></span>}
</div>

View file

@ -1,31 +0,0 @@
table.dataTable tbody > tr.selected {
background-color: rgb(218, 231, 255);
}
table.table-hover.dataTable tbody > tr.selected {
background-color: rgb(205, 212, 226);
}
table.table-hover.dataTable tbody {
cursor: default;
}
div.dataTables_wrapper .form-control {
border-color: #cccccc;
}
div.dataTables_wrapper .table-bordered {
border-radius: 3px;
border-color: #cccccc;
}
.has-error div.dataTables_wrapper .table-bordered {
border-color: #b94a48;
}
@media screen and (max-width: 767px) {
div.dataTables_wrapper div.dataTable_selection_info {
text-align: center;
}
}

View file

@ -11,8 +11,8 @@ import jQuery
from 'jquery';
import 'datatables.net';
import 'datatables.net-bs';
import 'datatables.net-bs/css/dataTables.bootstrap.css';
import 'datatables.net-bs4';
import 'datatables.net-bs4/css/dataTables.bootstrap4.css';
import axios
from './axios';

View file

@ -12,7 +12,7 @@ import jQuery
import '../../vendor/jquery/jquery-ui-1.12.1.min.js';
import '../../vendor/fancytree/jquery.fancytree-all.min.js';
import '../../vendor/fancytree/skin-bootstrap/ui.fancytree.min.css';
import './tree.css';
import './tree.scss';
import axios
from './axios';
@ -91,7 +91,8 @@ class TreeTable extends Component {
withHeader: PropTypes.bool,
withDescription: PropTypes.bool,
noTable: PropTypes.bool,
withIcons: PropTypes.bool
withIcons: PropTypes.bool,
className: PropTypes.string
}
componentWillReceiveProps(nextProps) {
@ -106,7 +107,7 @@ class TreeTable extends Component {
}
shouldComponentUpdate(nextProps, nextState) {
return this.props.selection !== nextProps.selection || this.state.treeData != nextState.treeData;
return this.props.selection !== nextProps.selection || this.state.treeData != nextState.treeData || this.props.className !== nextProps.className;
}
// XSS protection
@ -183,17 +184,16 @@ class TreeTable extends Component {
extensions: ['glyph'],
glyph: {
map: {
expanderClosed: 'glyphicon glyphicon-menu-right',
expanderLazy: 'glyphicon glyphicon-menu-right', // glyphicon-plus-sign
expanderOpen: 'glyphicon glyphicon-menu-down', // glyphicon-collapse-down
checkbox: 'glyphicon glyphicon-unchecked',
checkboxSelected: 'glyphicon glyphicon-check',
checkboxUnknown: 'glyphicon glyphicon-share',
expanderClosed: 'fas fa-angle-right',
expanderLazy: 'fas fa-angle-right', // glyphicon-plus-sign
expanderOpen: 'fas fa-angle-down', // glyphicon-collapse-down
checkbox: 'fas fa-square',
checkboxSelected: 'fas fa-check-square',
folder: 'glyphicon glyphicon-folder-close',
folderOpen: 'glyphicon glyphicon-folder-open',
doc: 'glyphicon glyphicon-file',
docOpen: 'glyphicon glyphicon-file'
folder: 'fas fa-folder',
folderOpen: 'fas fa-folder-open',
doc: 'fas fa-file',
docOpen: 'fas fa-file'
}
},
selectMode: (this.selectMode === TreeSelectMode.MULTI ? 2 : 1),
@ -220,9 +220,14 @@ class TreeTable extends Component {
this.updateSelection();
}
componentDidUpdate() {
this.tree.reload(this.sanitizeTreeData(this.state.treeData));
this.updateSelection();
componentDidUpdate(prevProps, prevState) {
if (this.props.selection !== prevProps.selection || this.state.treeData != prevState.treeData) {
if (this.state.treeData != prevState.treeData) {
this.tree.reload(this.sanitizeTreeData(this.state.treeData));
}
this.updateSelection();
}
}
updateSelection() {
@ -303,7 +308,7 @@ class TreeTable extends Component {
const withHeader = props.withHeader;
const withDescription = props.withDescription;
let containerClass = 'mt-treetable-container';
let containerClass = 'mt-treetable-container ' + (this.props.className || '');
if (this.selectMode === TreeSelectMode.NONE) {
containerClass += ' mt-treetable-inactivable';
} else {

View file

@ -1,3 +1,7 @@
@import "../../static/scss/variables.scss";
:global {
.mt-treetable-container .fancytree-container {
border: none;
}
@ -56,8 +60,11 @@
.form-group .mt-treetable-container {
border: 1px solid #cccccc;
border-radius: 4px;
border: $input-border-width solid $input-border-color;
border-radius: $input-border-radius;
padding-top: $input-padding-y;
padding-bottom: $input-padding-y;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
-webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
@ -65,15 +72,21 @@
transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
}
.form-group.has-success .mt-treetable-container {
border-color: #468847;
.form-group .mt-treetable-container.is-valid {
border-color: $form-feedback-valid-color;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
}
.form-group.has-error .mt-treetable-container {
border-color: #b94a48;
.form-group .mt-treetable-container.is-invalid {
border-color: $form-feedback-invalid-color;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
}
.mt-treetable-container .table td {
padding-top: 3px;
padding-bottom: 3px;
}
}

View file

@ -239,7 +239,7 @@ export default class CUD extends Component {
<CheckBox id="listunsubscribe_disabled" label={t('unsubscribeHeader')} text={t('doNotSendListUnsubscribeHeaders')}/>
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('save')}/>
{canDelete && <NavButton className="btn-danger" icon="remove" label={t('delete')} linkTo={`/lists/${this.props.entity.id}/delete`}/>}
</ButtonRow>
</Form>

View file

@ -108,14 +108,14 @@ export default class List extends Component {
if (perms.includes('viewSegments')) {
actions.push({
label: <Icon icon="tag" title={t('segments')}/>,
label: <Icon icon="tags" title={t('segments')}/>,
link: `/lists/${data[0]}/segments`
});
}
if (perms.includes('viewImports')) {
actions.push({
label: <Icon icon="sort" title={t('imports')}/>,
label: <Icon icon="file-import" title={t('imports')}/>,
link: `/lists/${data[0]}/imports`
});
}
@ -129,7 +129,7 @@ export default class List extends Component {
if (perms.includes('share')) {
actions.push({
label: <Icon icon="share-alt" title={t('share')}/>,
label: <Icon icon="share" title={t('share')}/>,
link: `/lists/${data[0]}/share`
});
}

View file

@ -480,7 +480,7 @@ export default class CUD extends Component {
}
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('save')}/>
{isEdit && <NavButton className="btn-danger" icon="remove" label={t('delete')} linkTo={`/lists/${this.props.list.id}/fields/${this.props.entity.id}/delete`}/>}
</ButtonRow>
</Form>

View file

@ -506,8 +506,8 @@ export default class CUD extends Component {
<div className={formsStyles.navbar}>
{this.state.fullscreen && <img className={formsStyles.logo} src={getTrustedUrl('static/mailtrain-notext.png')}/>}
<div className={formsStyles.title}>{t('formPreview') + ' ' + this.state.previewLabel}</div>
<a className={formsStyles.btn} onClick={() => this.setState({previewContents: null, previewFullscreen: false})}><Icon icon="remove"/></a>
<a className={formsStyles.btn} onClick={() => this.setState({previewFullscreen: !this.state.previewFullscreen})}><Icon icon="fullscreen"/></a>
<a className={formsStyles.btn} onClick={() => this.setState({previewContents: null, previewFullscreen: false})}><Icon icon="window-close"/></a>
<a className={formsStyles.btn} onClick={() => this.setState({previewFullscreen: !this.state.previewFullscreen})}><Icon icon="window-maximize"/></a>
</div>
<iframe className={formsStyles.host} src={"data:text/html;charset=utf-8," + encodeURIComponent(this.state.previewContents)}></iframe>
</div>
@ -524,7 +524,7 @@ export default class CUD extends Component {
}
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('save')}/>
{canDelete && <NavButton className="btn-danger" icon="remove" label={t('delete')} linkTo={`/lists/forms/${this.props.entity.id}/delete`}/>}
</ButtonRow>
</Form>

View file

@ -413,10 +413,10 @@ export default class CUD extends Component {
const saveButtons = []
if (!isEdit) {
saveButtons.push(<Button key="default" type="submit" className="btn-primary" icon="ok" label={t('saveAndEditSettings')}/>);
saveButtons.push(<Button key="default" type="submit" className="btn-primary" icon="check" label={t('saveAndEditSettings')}/>);
} else {
saveButtons.push(<Button key="default" type="submit" className="btn-primary" icon="ok" label={t('save')}/>);
saveButtons.push(<Button key="saveAndRun" className="btn-primary" icon="ok" label={t('saveAndRun')} onClickAsync={async () => await this.save(true)}/>);
saveButtons.push(<Button key="default" type="submit" className="btn-primary" icon="check" label={t('save')}/>);
saveButtons.push(<Button key="saveAndRun" className="btn-primary" icon="check" label={t('saveAndRun')} onClickAsync={async () => await this.save(true)}/>);
}
return (

View file

@ -354,14 +354,14 @@ export default class CUD extends Component {
<Form stateOwner={this} onSubmitAsync={::this.submitAndLeave}>
{isEdit ?
<ButtonRow format="wide" className={`col-xs-12 ${styles.toolbar}`}>
<FormButton type="submit" className="btn-primary" icon="ok" label={t('saveAndStay')} onClickAsync={::this.submitAndStay}/>
<FormButton type="submit" className="btn-primary" icon="ok" label={t('saveAndLeave')}/>
<FormButton type="submit" className="btn-primary" icon="check" label={t('saveAndStay')} onClickAsync={::this.submitAndStay}/>
<FormButton type="submit" className="btn-primary" icon="check" label={t('saveAndLeave')}/>
<NavButton className="btn-danger" icon="remove" label={t('delete')} linkTo={`/lists/${this.props.list.id}/segments/${this.props.entity.id}/delete`}/>
</ButtonRow>
:
<ButtonRow format="wide" className={`col-xs-12 ${styles.toolbar}`}>
<FormButton type="submit" className="btn-primary" icon="ok" label={t('save')}/>
<FormButton type="submit" className="btn-primary" icon="check" label={t('save')}/>
</ButtonRow>
}

View file

@ -222,7 +222,7 @@ export default class CUD extends Component {
</AlignedRow>
}
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('save')}/>
{isEdit && <NavButton className="btn-danger" icon="remove" label={t('delete')} linkTo={`/lists/${this.props.list.id}/subscriptions/${this.props.entity.id}/delete`}/>}
</ButtonRow>
</Form>

View file

@ -183,7 +183,7 @@ export default class List extends Component {
<div>
{tableRestActionDialogRender(this)}
<Toolbar>
<a href={getPublicUrl(`subscription/${this.props.list.cid}`, {withLocale: true})}><Button label={t('subscriptionForm')} className="btn-default"/></a>
<a href={getPublicUrl(`subscription/${this.props.list.cid}`, {withLocale: true})}><Button label={t('subscriptionForm')} className="btn-secondary"/></a>
<a href={getUrl(`subscriptions/export/${this.props.list.id}/`+ (this.props.segmentId || 0))}><Button label={t('exportAsCsv')} className="btn-primary"/></a>
<NavButton linkTo={`/lists/${this.props.list.id}/subscriptions/create`} className="btn-primary" icon="plus" label={t('addSubscriber')}/>
</Toolbar>

View file

@ -205,7 +205,7 @@ export default class CUD extends Component {
<TreeTableSelect id="namespace" label={t('parentNamespace')} data={this.state.treeData}/>}
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('save')}/>
{canDelete && <NavButton className="btn-danger" icon="remove" label={t('delete')} linkTo={`/namespaces/${this.props.entity.id}/delete`}/>}
</ButtonRow>
</Form>

View file

@ -274,7 +274,7 @@ export default class CUD extends Component {
}
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('save')}/>
{canDelete &&
<NavButton className="btn-danger" icon="remove" label={t('delete')} linkTo={`/reports/${this.props.entity.id}/delete`}/>
}

View file

@ -332,15 +332,15 @@ export default class CUD extends Component {
{isEdit ?
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('saveAndStay')} onClickAsync={::this.submitAndStay}/>
<Button type="submit" className="btn-primary" icon="ok" label={t('saveAndLeave')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('saveAndStay')} onClickAsync={::this.submitAndStay}/>
<Button type="submit" className="btn-primary" icon="check" label={t('saveAndLeave')}/>
{canDelete &&
<NavButton className="btn-danger" icon="remove" label={t('delete')} linkTo={`/reports/templates/${this.props.entity.id}/delete`}/>
}
</ButtonRow>
:
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('save')}/>
</ButtonRow>
}
</Form>

View file

@ -29,7 +29,9 @@ import settings
from './settings/root';
import {
MenuLink,
NavDropdown,
NavDropdownLink,
NavLink,
Section
} from "./lib/page";
@ -39,7 +41,6 @@ import Home
from "./Home";
import {
ActionLink,
DropdownMenuItem,
Icon
} from "./lib/bootstrap-components";
import {Link} from "react-router-dom";
@ -88,7 +89,7 @@ class Root extends Component {
const label = langDesc.getLabel(t);
languageOptions.push(
<li key={lng}><ActionLink onClickAsync={() => i18n.changeLanguage(langDesc.longCode)}>{label}</ActionLink></li>
<ActionLink key={lng} className="dropdown-item" onClickAsync={() => i18n.changeLanguage(langDesc.longCode)}>{label}</ActionLink>
)
}
@ -105,63 +106,55 @@ class Root extends Component {
const link = entry.link || entry.externalLink;
if (link && path.startsWith(link)) {
topLevelMenu.push(<MenuLink key={entryKey} className="active" to={link}>{entry.title} <span className="sr-only">{t('current')}</span></MenuLink>);
topLevelMenu.push(<NavLink key={entryKey} className="active" to={link}>{entry.title} <span className="sr-only">{t('current')}</span></NavLink>);
} else {
topLevelMenu.push(<MenuLink key={entryKey} to={link}>{entry.title}</MenuLink>);
topLevelMenu.push(<NavLink key={entryKey} to={link}>{entry.title}</NavLink>);
}
}
const languageChooser = (
<NavDropdown menuClassName="dropdown-menu-right" label={currentLngCode}>
{languageOptions}
</NavDropdown>
);
return (
<nav className="navbar navbar-default navbar-static-top">
<div className="container-fluid">
<div className="navbar-header">
<button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span className="sr-only">{t('toggleNavigation')}</span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
</button>
<Link className="navbar-brand" to="/"><Icon icon="envelope"/> Mailtrain</Link>
<nav className="navbar navbar-expand-lg navbar-dark bg-dark">
<Link className="navbar-brand" to="/"><Icon icon="envelope"/> Mailtrain</Link>
<button className="navbar-toggler" type="button" data-toggle="collapse" data-target="#mtMainNavbar" aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation">
<span className="navbar-toggler-icon"></span>
</button>
{mailtrainConfig.isAuthenticated ?
<div className="collapse navbar-collapse" id="mtMainNavbar">
<ul className="navbar-nav mr-auto">
{topLevelMenu}
<NavDropdown label={t('administration')}>
<NavDropdownLink to="/users">{t('users')}</NavDropdownLink>
<NavDropdownLink to="/namespaces">{t('namespaces')}</NavDropdownLink>
{mailtrainConfig.globalPermissions.manageSettings && <NavDropdownLink to="/settings">{t('globalSettings')}</NavDropdownLink>}
<NavDropdownLink to="/send-configurations">{t('sendConfigurations')}</NavDropdownLink>
{mailtrainConfig.globalPermissions.manageBlacklist && <NavDropdownLink to="/blacklist">{t('blacklist')}</NavDropdownLink>}
<NavDropdownLink to="/account/api">{t('api')}</NavDropdownLink>
</NavDropdown>
</ul>
<ul className="navbar-nav">
{languageChooser}
<NavDropdown menuClassName="dropdown-menu-right" label={mailtrainConfig.user.username} icon="user">
<NavDropdownLink to="/account"><Icon icon='user'/> {t('account')}</NavDropdownLink>
<ActionLink className="dropdown-item" onClickAsync={::this.logout}><Icon icon='sign-out-alt'/> {t('logOut')}</ActionLink>
</NavDropdown>
</ul>
</div>
{mailtrainConfig.isAuthenticated ?
<div className="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul className="nav navbar-nav">
{topLevelMenu}
<DropdownMenuItem label={t('administration')}>
<MenuLink to="/users"><Icon icon='cog'/> {t('users')}</MenuLink>
<MenuLink to="/namespaces"><Icon icon='cog'/> {t('namespaces')}</MenuLink>
{mailtrainConfig.globalPermissions.manageSettings && <MenuLink to="/settings"><Icon icon='cog'/> {t('globalSettings')}</MenuLink>}
<MenuLink to="/send-configurations"><Icon icon='cog'/> {t('sendConfigurations')}</MenuLink>
{mailtrainConfig.globalPermissions.manageBlacklist && <MenuLink to="/blacklist"><Icon icon='ban-circle'/> {t('blacklist')}</MenuLink>}
<MenuLink to="/account/api"><Icon icon='retweet'/> {t('api')}</MenuLink>
</DropdownMenuItem>
</ul>
<ul className="nav navbar-nav navbar-right">
<DropdownMenuItem label={currentLngCode}>
{languageOptions}
</DropdownMenuItem>
<DropdownMenuItem label={mailtrainConfig.user.username} icon="user">
<MenuLink to="/account"><Icon icon='user'/> {t('account')}</MenuLink>
<li>
<ActionLink onClickAsync={::this.logout}><Icon icon='log-out'/> {t('logOut')}</ActionLink>
</li>
</DropdownMenuItem>
</ul>
</div>
:
<div className="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul className="nav navbar-nav navbar-right">
<DropdownMenuItem label={currentLngCode}>
{languageOptions}
</DropdownMenuItem>
</ul>
</div>
}
</div>
:
<div className="collapse navbar-collapse" id="mtMainNavbar">
<ul className="navbar-nav mr-auto">
</ul>
<ul className="navbar-nav">
{languageChooser}
</ul>
</div>
}
</nav>
);
}

View file

@ -247,7 +247,7 @@ export default class CUD extends Component {
<hr/>
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('save')}/>
{canDelete &&
<NavButton className="btn-danger" icon="remove" label={t('delete')} linkTo={`/send-configurations/${this.props.entity.id}/delete`}/>
}

View file

@ -94,7 +94,7 @@ export default class Update extends Component {
<hr/>
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('save')}/>
</ButtonRow>
</Form>
</div>

View file

@ -168,7 +168,7 @@ export default class Share extends Component {
<TableSelect id="role" label={t('role')} withHeader dropdown dataUrl={`rest/shares-roles-table/${this.props.entityTypeId}`} columns={rolesColumns} selectionLabelIndex={1}/>
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('share')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('share')}/>
</ButtonRow>
</Form>

View file

@ -294,7 +294,7 @@ export default class CUD extends Component {
{editForm}
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={isEdit ? t('save') : t('saveAndEditTemplate')}/>
<Button type="submit" className="btn-primary" icon="check" label={isEdit ? t('save') : t('saveAndEditTemplate')}/>
{canDelete && <NavButton className="btn-danger" icon="remove" label={t('delete')} linkTo={`/templates/${this.props.entity.id}/delete`}/> }
{isEdit && <Button className="btn-danger" icon="send" label={t('testSend')} onClickAsync={async () => this.setState({showTestSendModal: true})}/> }
</ButtonRow>

View file

@ -451,7 +451,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
<div>
<AlignedRow>
<Button
className="btn-default"
className="btn-secondary"
onClickAsync={::owner.toggleMergeTagReference}
label={t('mergeTagReference')}/>
{owner.state.showMergeTagReference &&

View file

@ -193,15 +193,15 @@ export default class CUD extends Component {
{isEdit ?
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('saveAndStay')} onClickAsync={::this.submitAndStay}/>
<Button type="submit" className="btn-primary" icon="ok" label={t('saveAndLeave')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('saveAndStay')} onClickAsync={::this.submitAndStay}/>
<Button type="submit" className="btn-primary" icon="check" label={t('saveAndLeave')}/>
{canDelete &&
<NavButton className="btn-danger" icon="remove" label={t('delete')} linkTo={`/templates/mosaico/${this.props.entity.id}/delete`}/>
}
</ButtonRow>
:
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('save')}/>
</ButtonRow>
}
</Form>

View file

@ -248,7 +248,7 @@ export default class CUD extends Component {
<NamespaceSelect/>
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('save')}/>
{canDelete && <NavButton className="btn-danger" icon="remove" label={t('deleteUser')} linkTo={`/users/${this.props.entity.id}/delete`}/>}
</ButtonRow>
</Form>