Some refactoring to aling it more with IVIS and coreui theme.
This commit is contained in:
parent
397f85dac4
commit
c1731bf09f
50 changed files with 1427 additions and 2351 deletions
|
@ -1,51 +1,27 @@
|
|||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Account from './Account';
|
||||
import Login from './Login';
|
||||
import Reset from './Forgot';
|
||||
import ResetLink from './Reset';
|
||||
import API from './API';
|
||||
import mailtrainConfig from 'mailtrainConfig';
|
||||
import React
|
||||
from 'react';
|
||||
import Account
|
||||
from './Account';
|
||||
import API
|
||||
from './API';
|
||||
|
||||
|
||||
function getMenus(t) {
|
||||
const subPaths = {
|
||||
login: {
|
||||
title: t('signIn'),
|
||||
link: '/account/login',
|
||||
panelComponent: Login,
|
||||
},
|
||||
api: {
|
||||
title: t('api'),
|
||||
link: '/account/api',
|
||||
panelComponent: API
|
||||
}
|
||||
};
|
||||
|
||||
if (mailtrainConfig.isAuthMethodLocal) {
|
||||
subPaths.forgot = {
|
||||
title: t('passwordReset-1'),
|
||||
extraParams: [':username?'],
|
||||
link: '/account/forgot',
|
||||
panelComponent: Reset
|
||||
};
|
||||
|
||||
subPaths.reset = {
|
||||
title: t('passwordReset-1'),
|
||||
extraParams: [':username', ':resetToken'],
|
||||
link: '/account/reset',
|
||||
panelComponent: ResetLink
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
'account': {
|
||||
title: t('account'),
|
||||
link: '/account',
|
||||
panelComponent: Account,
|
||||
|
||||
children: subPaths
|
||||
children: {
|
||||
api: {
|
||||
title: t('api'),
|
||||
link: '/account/api',
|
||||
panelComponent: API
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
27
client/src/lib/bootstrap-components.js
vendored
27
client/src/lib/bootstrap-components.js
vendored
|
@ -114,7 +114,6 @@ class Button extends Component {
|
|||
class ButtonDropdown extends Component {
|
||||
static propTypes = {
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
noCaret: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
buttonClassName: PropTypes.string,
|
||||
menuClassName: PropTypes.string
|
||||
|
@ -170,6 +169,30 @@ class ActionLink extends Component {
|
|||
}
|
||||
|
||||
|
||||
class ButtonDropdownActionLink extends Component {
|
||||
static propTypes = {
|
||||
onClickAsync: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
disabled: PropTypes.bool
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
|
||||
let clsName = "dropdown-item ";
|
||||
if (props.disabled) {
|
||||
clsName += "disabled ";
|
||||
}
|
||||
|
||||
clsName += props.className;
|
||||
|
||||
return (
|
||||
<ActionLink className={clsName} onClickAsync={props.onClickAsync}>{props.children}</ActionLink>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation,
|
||||
withErrorHandling
|
||||
|
@ -289,8 +312,8 @@ class ModalDialog extends Component {
|
|||
export {
|
||||
Button,
|
||||
ButtonDropdown,
|
||||
DropdownMenuItem,
|
||||
ActionLink,
|
||||
ButtonDropdownActionLink,
|
||||
DismissibleAlert,
|
||||
ModalDialog,
|
||||
Icon
|
||||
|
|
|
@ -262,6 +262,8 @@ class RouteContent extends Component {
|
|||
|
||||
const primaryMenuComponent = React.createElement(route.primaryMenuComponent, primaryMenuProps);
|
||||
|
||||
let content = null;
|
||||
|
||||
if (resolved) {
|
||||
const compProps = {
|
||||
match: this.props.match,
|
||||
|
@ -276,10 +278,8 @@ class RouteContent extends Component {
|
|||
panel = route.panelRender(compProps);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{primaryMenuComponent}
|
||||
|
||||
content = (
|
||||
<>
|
||||
<div className={styles.breadcrumbAndSecondaryNavbar}>
|
||||
<Breadcrumb route={route} params={params} resolved={resolved}/>
|
||||
<SecondaryNavBar route={route} params={params} resolved={resolved}/>
|
||||
|
@ -289,18 +289,33 @@ class RouteContent extends Component {
|
|||
{this.props.flashMessage}
|
||||
{panel}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
{primaryMenuComponent}
|
||||
<div className="container-fluid">
|
||||
{t('loading')}
|
||||
</div>
|
||||
content = (
|
||||
<div className="container-fluid">
|
||||
{t('loading')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<header className="app-header">
|
||||
{primaryMenuComponent}
|
||||
</header>
|
||||
|
||||
<div className="app-body">
|
||||
<main className="main">
|
||||
{content}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer className="app-footer">
|
||||
<div className="text-muted">© 2018 <a href="https://mailtrain.org">Mailtrain.org</a>, <a href="mailto:info@mailtrain.org">info@mailtrain.org</a>. <a href="https://github.com/Mailtrain-org/mailtrain">{t('sourceOnGitHub')}</a></div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -350,14 +365,14 @@ export class SectionContent extends Component {
|
|||
|
||||
ensureAuthenticated() {
|
||||
if (!mailtrainConfig.isAuthenticated) {
|
||||
this.navigateTo('/account/login?next=' + encodeURIComponent(window.location.pathname));
|
||||
this.navigateTo('/login?next=' + encodeURIComponent(window.location.pathname));
|
||||
}
|
||||
}
|
||||
|
||||
errorHandler(error) {
|
||||
if (error instanceof interoperableErrors.NotLoggedInError) {
|
||||
if (window.location.pathname !== '/account/login') { // There may be multiple async requests failing at the same time. So we take the pathname only from the first one.
|
||||
this.navigateTo('/account/login?next=' + encodeURIComponent(window.location.pathname));
|
||||
if (window.location.pathname !== '/login') { // There may be multiple async requests failing at the same time. So we take the pathname only from the first one.
|
||||
this.navigateTo('/login?next=' + encodeURIComponent(window.location.pathname));
|
||||
}
|
||||
} else if (error.response && error.response.data && error.response.data.message) {
|
||||
console.error(error);
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
import {getUrl} from "./urls";
|
||||
|
||||
__webpack_public_path__ = getUrl('mailtrain/');
|
||||
__webpack_public_path__ = getUrl('client/');
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import "../../static/scss/variables";
|
||||
@import "../scss/variables.scss";
|
||||
|
||||
.form { // This is here to give the styles below higher priority than Bootstrap has
|
||||
:global .DayPicker {
|
||||
|
|
|
@ -9,9 +9,9 @@ import PropTypes
|
|||
|
||||
import jQuery
|
||||
from '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 '../../static/jquery/jquery-ui-1.12.1.min.js';
|
||||
import '../../static/fancytree/jquery.fancytree-all.min.js';
|
||||
import '../../static/fancytree/skin-bootstrap/ui.fancytree.min.css';
|
||||
import './tree.scss';
|
||||
import axios
|
||||
from './axios';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import "../../static/scss/variables.scss";
|
||||
@import "../scss/variables.scss";
|
||||
|
||||
:global {
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ export default class Forget extends Component {
|
|||
const submitSuccessful = await this.validateAndSendFormValuesToURL(FormSendMethod.POST, 'rest/password-reset-send');
|
||||
|
||||
if (submitSuccessful) {
|
||||
this.navigateToWithFlashMessage('/account/login', 'success', t('ifTheUsernameEmailExistsInTheSystem'));
|
||||
this.navigateToWithFlashMessage('/login', 'success', t('ifTheUsernameEmailExistsInTheSystem'));
|
||||
} else {
|
||||
this.enableForm();
|
||||
this.setFormStatusMessage('warning', t('pleaseEnterYourUsernameEmailAndTryAgain'));
|
|
@ -106,7 +106,7 @@ export default class Login extends Component {
|
|||
|
||||
let passwordResetLink;
|
||||
if (mailtrainConfig.isAuthMethodLocal) {
|
||||
passwordResetLink = <Link to={`/account/forgot/${this.getFormValue('username')}`}>{t('forgotYourPassword?')}</Link>;
|
||||
passwordResetLink = <Link to={`/login/forgot/${this.getFormValue('username')}`}>{t('forgotYourPassword?')}</Link>;
|
||||
} else if (mailtrainConfig.externalPasswordResetLink) {
|
||||
passwordResetLink = <a href={mailtrainConfig.externalPasswordResetLink}>{t('forgotYourPassword?')}</a>;
|
||||
}
|
|
@ -114,7 +114,7 @@ export default class Account extends Component {
|
|||
});
|
||||
|
||||
if (submitSuccessful) {
|
||||
this.navigateToWithFlashMessage('/account/login', 'success', t('passwordReset-1'));
|
||||
this.navigateToWithFlashMessage('/login', 'success', t('passwordReset-1'));
|
||||
} else {
|
||||
this.enableForm();
|
||||
this.setFormStatusMessage('warning', t('thereAreErrorsInTheFormPleaseFixThemAnd'));
|
||||
|
@ -124,7 +124,7 @@ export default class Account extends Component {
|
|||
this.setFormStatusMessage('danger',
|
||||
<span>
|
||||
<strong>{t('yourPasswordCannotBeReset')}</strong>{' '}
|
||||
{t('thePasswordResetTokenHasExpired')}{' '}<Link to={`/account/forgot/${this.getFormValue('username')}`}>{t('clickHereToRequestANewPasswordResetLink')}</Link>
|
||||
{t('thePasswordResetTokenHasExpired')}{' '}<Link to={`/login/forgot/${this.getFormValue('username')}`}>{t('clickHereToRequestANewPasswordResetLink')}</Link>
|
||||
</span>
|
||||
);
|
||||
return;
|
||||
|
@ -147,7 +147,7 @@ export default class Account extends Component {
|
|||
<div>
|
||||
<Title>{t('thePasswordCannotBeReset')}</Title>
|
||||
|
||||
<p>{t('thePasswordResetTokenHasExpired')}{' '}<Link to={`/account/forgot/${this.getFormValue('username')}`}>{t('clickHereToRequestANewPasswordResetLink')}</Link></p>
|
||||
<p>{t('thePasswordResetTokenHasExpired')}{' '}<Link to={`/login/forgot/${this.getFormValue('username')}`}>{t('clickHereToRequestANewPasswordResetLink')}</Link></p>
|
||||
</div>
|
||||
);
|
||||
|
47
client/src/login/root.js
Normal file
47
client/src/login/root.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
'use strict';
|
||||
|
||||
import React
|
||||
from 'react';
|
||||
import Login
|
||||
from './Login';
|
||||
import Reset
|
||||
from './Forgot';
|
||||
import ResetLink
|
||||
from './Reset';
|
||||
import mailtrainConfig
|
||||
from 'mailtrainConfig';
|
||||
|
||||
|
||||
function getMenus(t) {
|
||||
const subPaths = {}
|
||||
|
||||
if (mailtrainConfig.isAuthMethodLocal) {
|
||||
subPaths.forgot = {
|
||||
title: t('passwordReset-1'),
|
||||
extraParams: [':username?'],
|
||||
link: '/login/forgot',
|
||||
panelComponent: Reset
|
||||
};
|
||||
|
||||
subPaths.reset = {
|
||||
title: t('passwordReset-1'),
|
||||
extraParams: [':username', ':resetToken'],
|
||||
link: '/login/reset',
|
||||
panelComponent: ResetLink
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
'login': {
|
||||
title: t('signIn'),
|
||||
link: '/login',
|
||||
panelComponent: Login,
|
||||
|
||||
children: subPaths
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
getMenus
|
||||
}
|
|
@ -9,6 +9,8 @@ import {I18nextProvider} from 'react-i18next';
|
|||
import i18n, {withTranslation} from './lib/i18n';
|
||||
import account
|
||||
from './account/root';
|
||||
import login
|
||||
from './login/root';
|
||||
import blacklist
|
||||
from './blacklist/root';
|
||||
import lists
|
||||
|
@ -41,6 +43,7 @@ import Home
|
|||
from "./Home";
|
||||
import {
|
||||
ActionLink,
|
||||
ButtonDropdownActionLink,
|
||||
Icon
|
||||
} from "./lib/bootstrap-components";
|
||||
import {Link} from "react-router-dom";
|
||||
|
@ -89,7 +92,7 @@ class Root extends Component {
|
|||
const label = langDesc.getLabel(t);
|
||||
|
||||
languageOptions.push(
|
||||
<ActionLink key={lng} className="dropdown-item" onClickAsync={() => i18n.changeLanguage(langDesc.longCode)}>{label}</ActionLink>
|
||||
<ButtonDropdownActionLink key={lng} onClickAsync={() => i18n.changeLanguage(langDesc.longCode)}>{label}</ButtonDropdownActionLink>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -166,6 +169,7 @@ class Root extends Component {
|
|||
panelComponent: Home,
|
||||
primaryMenuComponent: MainMenu,
|
||||
children: {
|
||||
...login.getMenus(t),
|
||||
...lists.getMenus(t),
|
||||
...reports.getMenus(t),
|
||||
...templates.getMenus(t),
|
||||
|
@ -180,15 +184,7 @@ class Root extends Component {
|
|||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Section root='/' structure={structure}/>
|
||||
|
||||
<footer className="footer">
|
||||
<div className="container-fluid">
|
||||
<p className="text-muted">© 2018 <a href="https://mailtrain.org">Mailtrain.org</a>, <a href="mailto:info@mailtrain.org">info@mailtrain.org</a>. <a href="https://github.com/Mailtrain-org/mailtrain">{t('sourceOnGitHub')}</a></p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<Section root='/' structure={structure}/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
1
client/src/scss/.gitignore
vendored
Normal file
1
client/src/scss/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/coreui
|
51
client/src/scss/mailtrain.scss
Normal file
51
client/src/scss/mailtrain.scss
Normal file
|
@ -0,0 +1,51 @@
|
|||
$fa-font-path: "../static-npm/fontawesome";
|
||||
|
||||
@import "./variables.scss";
|
||||
@import "node_modules/@coreui/coreui/scss/coreui.scss";
|
||||
@import "node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss";
|
||||
@import "node_modules/@fortawesome/fontawesome-free/scss/regular.scss";
|
||||
@import "node_modules/@fortawesome/fontawesome-free/scss/solid.scss";
|
||||
|
||||
.custom-select {
|
||||
-webkit-appearance: none; // This is a fix for Chrome
|
||||
}
|
||||
|
||||
body.mailtrain {
|
||||
background-color: white;
|
||||
|
||||
.app-header {
|
||||
height: auto;
|
||||
padding: 0;
|
||||
flex: none;
|
||||
border-bottom: 0px none;
|
||||
|
||||
.navbar-brand {
|
||||
width: auto;
|
||||
height: auto;
|
||||
display: inline-block;
|
||||
padding-top: $navbar-brand-padding-y;
|
||||
padding-bottom: $navbar-brand-padding-y;
|
||||
margin-right: $navbar-padding-x;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
min-width: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.main .container-fluid {
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.navbar-dark {
|
||||
.navbar-nav {
|
||||
.active > .nav-link:hover {
|
||||
color: $navbar-dark-active-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gpg-text {
|
||||
font-family: monospace;
|
||||
}
|
||||
}
|
9
client/src/scss/variables.scss
Normal file
9
client/src/scss/variables.scss
Normal file
|
@ -0,0 +1,9 @@
|
|||
$table-cell-padding: .5rem;
|
||||
$input-border-color: #adb5bd;
|
||||
|
||||
$breadcrumb-bg: #f6f7f8;
|
||||
|
||||
$navbar-dark-color: rgba(#fff, .75) !default;
|
||||
$navbar-dark-hover-color: #fff !default;
|
||||
|
||||
@import "../../node_modules/@coreui/coreui/scss/_variables.scss";
|
|
@ -607,7 +607,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
|
|||
height="400px"
|
||||
mode="text"
|
||||
label={t('templateContentPlainText')}
|
||||
help={<Trans i18nKey="toExtractTheTextFromHtmlClickHerePlease">To extract the text from HTML click <ActionLink onClickAsync={::owner.extractPlainText}>here</ActionLink>. Please note that your existing plaintext in the field above will be overwritten. This feature uses the <a href="http://premailer.dialect.ca/api">Premailer API</a>, a third party service. Their Terms of Service and Privacy Policy apply.</Trans>}
|
||||
help={<Trans i18nKey="toExtractTheTextFromHtmlClickHerePlease">To extract the text from HTML click <ActionLink onClickAsync={::owner.extractPlainText}>here</ActionLink>. Please note that your existing plaintext in the field above will be overwritten. This feature uses the <a href="http://premailer.dialect.ca/api">Premailer API</a>, a third party service. Their Terms of Service and Privacy Policy apply.</Trans>}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue