Load users and email

This commit is contained in:
soupette 2019-04-16 16:55:53 +02:00
parent e1d619065b
commit 61f115fbee
39 changed files with 922 additions and 422 deletions

View File

@ -40,6 +40,9 @@ import { translationMessages, languages } from './i18n';
// Create redux store with history // Create redux store with history
import history from './utils/history'; import history from './utils/history';
import plugins from './plugins';
const initialState = {}; const initialState = {};
const store = configureStore(initialState, history); const store = configureStore(initialState, history);
const { dispatch } = store; const { dispatch } = store;
@ -47,7 +50,18 @@ const MOUNT_NODE = document.getElementById('app');
// TODO remove temporary to access the admin // TODO remove temporary to access the admin
dispatch(getAppPluginsSucceeded([])); dispatch(getAppPluginsSucceeded(Object.keys(plugins)));
Object.keys(plugins).forEach(plugin => {
const currentPlugin = plugins[plugin];
try {
merge(translationMessages, currentPlugin.translationMessages);
dispatch(pluginLoaded(currentPlugin));
} catch (err) {
console.log({ err });
}
});
// TODO // TODO
const remoteURL = (() => { const remoteURL = (() => {
@ -63,14 +77,6 @@ const remoteURL = (() => {
return process.env.REMOTE_URL.replace(/\/$/, ''); return process.env.REMOTE_URL.replace(/\/$/, '');
})(); })();
const registerPlugin = plugin => {
// Merge admin translation messages
merge(translationMessages, plugin.translationMessages);
plugin.leftMenuSections = plugin.leftMenuSections || [];
dispatch(pluginLoaded(plugin));
};
const displayNotification = (message, status) => { const displayNotification = (message, status) => {
dispatch(showNotification(message, status)); dispatch(showNotification(message, status));
}; };
@ -85,7 +91,6 @@ window.strapi = Object.assign(window.strapi || {}, {
node: MODE || 'host', node: MODE || 'host',
remoteURL, remoteURL,
backendURL: BACKEND_URL, backendURL: BACKEND_URL,
registerPlugin,
notification: { notification: {
success: message => { success: message => {
displayNotification(message, 'success'); displayNotification(message, 'success');

View File

@ -16,9 +16,7 @@ import styles from './styles.scss';
class OnboardingVideo extends React.Component { class OnboardingVideo extends React.Component {
componentDidMount() { componentDidMount() {
this.hiddenPlayer.current.subscribeToStateChange( this.hiddenPlayer.current.subscribeToStateChange(this.handleChangeState);
this.handleChangeState,
);
} }
hiddenPlayer = React.createRef(); hiddenPlayer = React.createRef();
@ -28,7 +26,7 @@ class OnboardingVideo extends React.Component {
handleChangeState = (state, prevState) => { handleChangeState = (state, prevState) => {
const { duration } = state; const { duration } = state;
const { id } = this.props; const { id } = this.props;
if (duration !== prevState.duration) { if (duration !== prevState.duration) {
this.props.setVideoDuration(id, duration); this.props.setVideoDuration(id, duration);
} }
@ -43,14 +41,16 @@ class OnboardingVideo extends React.Component {
} }
}; };
handleCurrentTimeChange = (curr) => { handleCurrentTimeChange = curr => {
this.props.getVideoCurrentTime(this.props.id, curr, this.props.video.duration); this.props.getVideoCurrentTime(
} this.props.id,
curr,
this.props.video.duration,
);
};
handleModalOpen = () => { handleModalOpen = () => {
this.player.current.subscribeToStateChange( this.player.current.subscribeToStateChange(this.handleChangeIsPlayingState);
this.handleChangeIsPlayingState,
);
this.player.current.play(); this.player.current.play();
@ -89,7 +89,7 @@ class OnboardingVideo extends React.Component {
key={this.props.id} key={this.props.id}
onClick={this.props.onClick} onClick={this.props.onClick}
id={this.props.id} id={this.props.id}
className={cn(styles.listItem, video.end && (styles.finished))} className={cn(styles.listItem, video.end && styles.finished)}
> >
<div className={styles.thumbWrapper}> <div className={styles.thumbWrapper}>
<img src={video.preview} alt="preview" /> <img src={video.preview} alt="preview" />
@ -98,9 +98,15 @@ class OnboardingVideo extends React.Component {
</div> </div>
<div className={styles.txtWrapper}> <div className={styles.txtWrapper}>
<p className={styles.title}>{video.title}</p> <p className={styles.title}>{video.title}</p>
<p className={styles.time}>{isNaN(video.duration) ? '\xA0' : `${Math.floor(video.duration / 60)}:${Math.floor(video.duration)%60}`}</p> <p className={styles.time}>
{isNaN(video.duration)
? '\xA0'
: `${Math.floor(video.duration / 60)}:${Math.floor(
video.duration,
) % 60}`}
</p>
</div> </div>
<Modal <Modal
isOpen={video.isOpen} isOpen={video.isOpen}
toggle={this.props.onClick} // eslint-disable-line react/jsx-handler-names toggle={this.props.onClick} // eslint-disable-line react/jsx-handler-names
@ -119,7 +125,7 @@ class OnboardingVideo extends React.Component {
<Player <Player
ref={this.player} ref={this.player}
className={styles.videoPlayer} className={styles.videoPlayer}
poster="/assets/poster.png" // poster="/assets/poster.png"
src={video.video} src={video.video}
startTime={video.startTime} startTime={video.startTime}
preload="auto" preload="auto"
@ -135,7 +141,7 @@ class OnboardingVideo extends React.Component {
<div className={cn(styles.hiddenPlayerWrapper)}> <div className={cn(styles.hiddenPlayerWrapper)}>
<Player <Player
ref={this.hiddenPlayer} ref={this.hiddenPlayer}
poster="/assets/poster.png" // poster="/assets/poster.png"
src={video.video} src={video.video}
preload="auto" preload="auto"
subscribeToStateChange={this.subscribeToStateChange} subscribeToStateChange={this.subscribeToStateChange}

View File

@ -7,6 +7,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import cn from 'classnames'; import cn from 'classnames';
import { withRouter } from 'react-router-dom';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { includes, isEmpty } from 'lodash'; import { includes, isEmpty } from 'lodash';
@ -48,7 +49,7 @@ class Row extends React.Component {
onClick: e => { onClick: e => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
this.context.router.history.push(settingsPath); this.props.history.push(settingsPath);
}, },
}, },
{ {
@ -106,7 +107,6 @@ class Row extends React.Component {
Row.contextTypes = { Row.contextTypes = {
currentEnvironment: PropTypes.string, currentEnvironment: PropTypes.string,
router: PropTypes.object,
}; };
Row.propTypes = { Row.propTypes = {
@ -117,4 +117,4 @@ Row.propTypes = {
pluginActionSucceeded: PropTypes.bool.isRequired, pluginActionSucceeded: PropTypes.bool.isRequired,
}; };
export default Row; export default withRouter(Row);

View File

@ -18,7 +18,7 @@ const initialState = fromJS({
blockApp: false, blockApp: false,
overlayBlockerData: null, overlayBlockerData: null,
hasUserPlugin: true, hasUserPlugin: true,
isAppLoading: false, isAppLoading: true,
plugins: {}, plugins: {},
showGlobalAppBlocker: true, showGlobalAppBlocker: true,
}); });

View File

@ -14,12 +14,19 @@ import { IntlProvider } from 'react-intl';
import { defaultsDeep } from 'lodash'; import { defaultsDeep } from 'lodash';
import { selectLocale } from './selectors'; import { selectLocale } from './selectors';
export class LanguageProvider extends React.Component { // eslint-disable-line react/prefer-stateless-function export class LanguageProvider extends React.Component {
// eslint-disable-line react/prefer-stateless-function
render() { render() {
const messages = defaultsDeep(this.props.messages[this.props.locale], this.props.messages.en); const messages = defaultsDeep(
this.props.messages[this.props.locale],
this.props.messages.en,
);
return ( return (
<IntlProvider locale={this.props.locale} defaultLocale="en" messages={messages}> <IntlProvider
locale={this.props.locale}
defaultLocale="en"
messages={messages}
>
{React.Children.only(this.props.children)} {React.Children.only(this.props.children)}
</IntlProvider> </IntlProvider>
); );
@ -32,10 +39,9 @@ LanguageProvider.propTypes = {
messages: PropTypes.object.isRequired, messages: PropTypes.object.isRequired,
}; };
const mapStateToProps = createSelector( const mapStateToProps = createSelector(
selectLocale(), selectLocale(),
(locale) => ({ locale }) locale => ({ locale }),
); );
function mapDispatchToProps(dispatch) { function mapDispatchToProps(dispatch) {
@ -44,4 +50,7 @@ function mapDispatchToProps(dispatch) {
}; };
} }
export default connect(mapStateToProps, mapDispatchToProps)(LanguageProvider); export default connect(
mapStateToProps,
mapDispatchToProps,
)(LanguageProvider);

View File

@ -0,0 +1,23 @@
// console.log(window);
const injectReducer = require('./utils/injectReducer').default;
const injectSaga = require('./utils/injectSaga').default;
const { languages } = require('./i18n');
window.strapi = Object.assign(window.strapi || {}, {
node: MODE || 'host',
backendURL: BACKEND_URL,
languages,
currentLanguage:
window.localStorage.getItem('strapi-admin-language') ||
window.navigator.language ||
window.navigator.userLanguage ||
'en',
injectReducer,
injectSaga,
});
module.exports = {
email: require('../../../strapi-plugin-email/admin/src').default,
'users-permissions': require('../../../strapi-plugin-users-permissions/admin/src')
.default,
};

View File

@ -28,11 +28,11 @@
}, },
"dependencies": { "dependencies": {
"@babel/polyfill": "^7.4.3", "@babel/polyfill": "^7.4.3",
"@babel/runtime": "^7.4.3",
"classnames": "^2.2.6",
"crypto": "^1.0.1", "crypto": "^1.0.1",
"friendly-errors-webpack-plugin": "^1.7.0",
"history": "^4.9.0", "history": "^4.9.0",
"hoist-non-react-statics": "^3.3.0", "hoist-non-react-statics": "^3.3.0",
"html-webpack-plugin": "^3.2.0",
"immutable": "^3.8.2", "immutable": "^3.8.2",
"intl": "^1.2.5", "intl": "^1.2.5",
"invariant": "^2.2.4", "invariant": "^2.2.4",
@ -56,7 +56,6 @@
"redux-saga": "^0.16.0", "redux-saga": "^0.16.0",
"remove-markdown": "^0.2.2", "remove-markdown": "^0.2.2",
"reselect": "^3.0.1", "reselect": "^3.0.1",
"shelljs": "^0.7.8",
"strapi-helper-plugin": "3.0.0-alpha.25.2", "strapi-helper-plugin": "3.0.0-alpha.25.2",
"video-react": "^0.13.2" "video-react": "^0.13.2"
}, },
@ -74,7 +73,9 @@
"css-loader": "^2.1.1", "css-loader": "^2.1.1",
"duplicate-package-checker-webpack-plugin": "^3.0.0", "duplicate-package-checker-webpack-plugin": "^3.0.0",
"file-loader": "^3.0.1", "file-loader": "^3.0.1",
"friendly-errors-webpack-plugin": "^1.7.0",
"html-loader": "^0.5.5", "html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"image-webpack-loader": "^4.6.0", "image-webpack-loader": "^4.6.0",
"mini-css-extract-plugin": "^0.6.0", "mini-css-extract-plugin": "^0.6.0",
"node-sass": "^4.11.0", "node-sass": "^4.11.0",
@ -84,6 +85,7 @@
"precss": "^4.0.0", "precss": "^4.0.0",
"sanitize.css": "^4.1.0", "sanitize.css": "^4.1.0",
"sass-loader": "^7.1.0", "sass-loader": "^7.1.0",
"shelljs": "^0.7.8",
"simple-progress-webpack-plugin": "^1.1.2", "simple-progress-webpack-plugin": "^1.1.2",
"strapi-utils": "3.0.0-alpha.25.2", "strapi-utils": "3.0.0-alpha.25.2",
"style-loader": "^0.23.1", "style-loader": "^0.23.1",
@ -110,4 +112,4 @@
"npm": ">= 6.0.0" "npm": ">= 6.0.0"
}, },
"license": "MIT" "license": "MIT"
} }

View File

@ -1,14 +1,10 @@
const path = require('path'); const path = require('path');
const alias = [ const pkg = require('./package.json');
'core-js',
'create-react-context', const alias = Object.keys(pkg.dependencies).concat([
'invariant',
'hoist-non-react-statics',
'object-assign', 'object-assign',
'react-popper',
'reactstrap',
'whatwg-fetch', 'whatwg-fetch',
]; ]);
module.exports = alias.reduce((acc, curr) => { module.exports = alias.reduce((acc, curr) => {
acc[curr] = path.resolve(__dirname, 'node_modules', curr); acc[curr] = path.resolve(__dirname, 'node_modules', curr);

View File

@ -91,11 +91,6 @@ module.exports = {
{ {
test: /\.m?js$/, test: /\.m?js$/,
exclude: /node_modules/, exclude: /node_modules/,
include: [
path.resolve(__dirname, 'admin/src'),
path.resolve(__dirname, '../strapi-helper-plugin/lib/src'),
path.resolve(__dirname, 'node_modules/strapi-helper-plugin/lib/src'),
],
use: { use: {
loader: require.resolve('babel-loader'), loader: require.resolve('babel-loader'),
options: { options: {

View File

@ -107,6 +107,7 @@ strapi.registerPlugin({
layout, layout,
lifecycles, lifecycles,
leftMenuLinks: [], leftMenuLinks: [],
leftMenuSections: [],
mainComponent: Comp, mainComponent: Comp,
name: pluginPkg.strapi.name, name: pluginPkg.strapi.name,
preventComponentRendering: false, preventComponentRendering: false,

View File

@ -1,3 +1,3 @@
.helperContainerFluid { .helperContainerFluid {
padding: 18px 30px; padding: 18px 30px !important;
} }

View File

@ -1,5 +1,5 @@
.headerNav { /* stylelint-disable */ .headerNav {
/* stylelint-disable */
} }
.headerContainer { .headerContainer {
@ -8,10 +8,11 @@
overflow: hidden; overflow: hidden;
margin-top: 4.3rem; margin-top: 4.3rem;
border-radius: 2px; border-radius: 2px;
box-shadow: 0 2px 4px #E3E9F3; box-shadow: 0 2px 4px #e3e9f3;
> a { > a {
box-shadow: 1px 0 0px rgba(#DBDBDB, 0.5), inset 0px -1px 0px -2px rgba(#DBDBDB, 0.5); box-shadow: 1px 0 0px rgba(#dbdbdb, 0.5),
background-color: #F5F5F5; inset 0px -1px 0px -2px rgba(#dbdbdb, 0.5);
background-color: #f5f5f5;
&:first-child { &:first-child {
border-radius: 2px 0 0 0; border-radius: 2px 0 0 0;
} }
@ -28,7 +29,7 @@
// max-width: 33%; // max-width: 33%;
height: 3.6rem; height: 3.6rem;
border-radius: 2px 0 0 0; border-radius: 2px 0 0 0;
background-color: darken(#F5F5F5, 50%); background-color: darken(#f5f5f5, 50%);
text-decoration: none !important; text-decoration: none !important;
font-family: Lato; font-family: Lato;
font-size: 1.3rem; font-size: 1.3rem;
@ -38,30 +39,37 @@
.linkActive { .linkActive {
z-index: 10; z-index: 10;
&:not(:first-child, :last-child) {
background-color: #ffffff !important;
font-weight: bold;
text-decoration: none !important;
box-shadow: 0 0 2px rgba(#dbdbdb, 0.5);
border-top: 0.2rem solid #1c5de7;
}
} }
.linkActive:first-child { .linkActive:first-child {
background-color: #FFFFFF!important; background-color: #ffffff !important;
font-weight: bold; font-weight: bold;
text-decoration: none !important; text-decoration: none !important;
box-shadow: 0 0 2px rgba(#DBDBDB, 0.5); box-shadow: 0 0 2px rgba(#dbdbdb, 0.5);
border-top: 0.2rem solid #1C5DE7; border-top: 0.2rem solid #1c5de7;
} }
.linkActive:last-child { .linkActive:last-child {
background-color: #FFFFFF!important; background-color: #ffffff !important;
font-weight: bold; font-weight: bold;
text-decoration: none !important; text-decoration: none !important;
box-shadow: 0 0 2px rgba(#DBDBDB, 0.5); box-shadow: 0 0 2px rgba(#dbdbdb, 0.5);
border-top: 0.2rem solid #1C5DE7; border-top: 0.2rem solid #1c5de7;
} }
.linkActive:not(:first-child, :last-child) { .linkActive:not(:first-child, :last-child) {
background-color: #FFFFFF!important; background-color: #ffffff !important;
font-weight: bold; font-weight: bold;
text-decoration: none !important; text-decoration: none !important;
box-shadow: 0 0 2px rgba(#DBDBDB, 0.5); box-shadow: 0 0 2px rgba(#dbdbdb, 0.5);
border-top: 0.2rem solid #1C5DE7; border-top: 0.2rem solid #1c5de7;
} }
.linkText { .linkText {
@ -71,10 +79,10 @@
} }
.notifPoint { .notifPoint {
height: 0.4rem; height: 0.4rem;
width: 0.4rem; width: 0.4rem;
margin: 1px 0 0 0.7rem; margin: 1px 0 0 0.7rem;
align-self: center; align-self: center;
border-radius: 0.5rem; border-radius: 0.5rem;
background-color: #27B70F; background-color: #27b70f;
} }

View File

@ -1,9 +1,9 @@
.textInput { .textInput {
height: 3.4rem; height: 3.4rem;
margin-top: .9rem; margin-top: 0.9rem;
padding-left: 1rem; padding-left: 1rem;
background-size: 0 !important; background-size: 0 !important;
border: 1px solid #E3E9F3; border: 1px solid #e3e9f3;
border-radius: 0.25rem; border-radius: 0.25rem;
line-height: 3.4rem; line-height: 3.4rem;
font-size: 1.3rem; font-size: 1.3rem;

View File

@ -12,6 +12,7 @@ export {
export { default as ErrorBoundary } from './components/ErrorBoundary'; export { default as ErrorBoundary } from './components/ErrorBoundary';
export { default as ExtendComponent } from './components/ExtendComponent'; export { default as ExtendComponent } from './components/ExtendComponent';
export { default as GlobalPagination } from './components/GlobalPagination'; export { default as GlobalPagination } from './components/GlobalPagination';
export { default as HeaderNav } from './components/HeaderNav';
export { default as IcoContainer } from './components/IcoContainer'; export { default as IcoContainer } from './components/IcoContainer';
export { default as InputAddon } from './components/InputAddon'; export { default as InputAddon } from './components/InputAddon';
@ -22,10 +23,10 @@ export { default as InputCheckbox } from './components/InputCheckbox';
export { export {
default as InputCheckboxWithErrors, default as InputCheckboxWithErrors,
} from './components/InputCheckboxWithErrors'; } from './components/InputCheckboxWithErrors';
// export { default as InputDate } from './components/InputDate'; export { default as InputDate } from './components/InputDate';
// export { export {
// default as InputDateWithErrors, default as InputDateWithErrors,
// } from './components/InputDateWithErrors'; } from './components/InputDateWithErrors';
export { default as InputEmail } from './components/InputEmail'; export { default as InputEmail } from './components/InputEmail';
export { export {
default as InputEmailWithErrors, default as InputEmailWithErrors,

View File

@ -67,6 +67,7 @@
"rollup-plugin-postcss": "^2.0.3", "rollup-plugin-postcss": "^2.0.3",
"rollup-plugin-sass": "^1.2.2", "rollup-plugin-sass": "^1.2.2",
"rollup-plugin-scss": "^1.0.1", "rollup-plugin-scss": "^1.0.1",
"rollup-plugin-sizes": "^0.5.1",
"rollup-plugin-svg": "^1.0.1", "rollup-plugin-svg": "^1.0.1",
"shelljs": "^0.7.8" "shelljs": "^0.7.8"
}, },
@ -90,4 +91,4 @@
"styled-components": "^3.2.6", "styled-components": "^3.2.6",
"whatwg-fetch": "^2.0.3" "whatwg-fetch": "^2.0.3"
} }
} }

View File

@ -14,6 +14,7 @@ export default {
format: 'cjs', format: 'cjs',
sourceMap: true, sourceMap: true,
name: 'strapi-helper-plugin', name: 'strapi-helper-plugin',
compact: true,
}, },
{ {
exports: 'named', exports: 'named',
@ -21,6 +22,7 @@ export default {
file: pkg.module, file: pkg.module,
format: 'es', format: 'es',
name: 'strapi-helper-plugin', name: 'strapi-helper-plugin',
compact: true,
}, },
], ],
@ -34,6 +36,7 @@ export default {
resolve(), resolve(),
commonjs(), commonjs(),
svg(), svg(),
require('rollup-plugin-sizes')(),
], ],
external: [ external: [

View File

@ -7,26 +7,30 @@
import React from 'react'; import React from 'react';
import { findIndex, get, isEmpty, map } from 'lodash'; import { findIndex, get, isEmpty, map } from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
// You can find these components in either import { InputsIndex as Input } from 'strapi-helper-plugin';
// ./node_modules/strapi-helper-plugin/lib/src
// or strapi/packages/strapi-helper-plugin/lib/src
import Input from 'components/InputsIndex';
import styles from './styles.scss'; import styles from './styles.scss';
class EditForm extends React.Component { class EditForm extends React.Component {
getProviderForm = () => get(this.props.settings, ['providers', this.props.selectedProviderIndex, 'auth'], {}); getProviderForm = () =>
get(
this.props.settings,
['providers', this.props.selectedProviderIndex, 'auth'],
{},
);
generateSelectOptions = () => ( generateSelectOptions = () =>
Object.keys(get(this.props.settings, 'providers', {})).reduce((acc, current) => { Object.keys(get(this.props.settings, 'providers', {})).reduce(
const option = { (acc, current) => {
id: get(this.props.settings, ['providers', current, 'name']), const option = {
value: get(this.props.settings, ['providers', current, 'provider']), id: get(this.props.settings, ['providers', current, 'name']),
}; value: get(this.props.settings, ['providers', current, 'provider']),
acc.push(option); };
return acc; acc.push(option);
}, []) return acc;
) },
[],
);
render() { render() {
return ( return (
@ -34,7 +38,9 @@ class EditForm extends React.Component {
<div className="row"> <div className="row">
<Input <Input
customBootstrapClass="col-md-6" customBootstrapClass="col-md-6"
inputDescription={{ id: 'email.EditForm.Input.select.inputDescription' }} inputDescription={{
id: 'email.EditForm.Input.select.inputDescription',
}}
inputClassName={styles.inputStyle} inputClassName={styles.inputStyle}
label={{ id: 'email.EditForm.Input.select.label' }} label={{ id: 'email.EditForm.Input.select.label' }}
name="provider" name="provider"
@ -50,7 +56,10 @@ class EditForm extends React.Component {
{map(this.getProviderForm(), (value, key) => ( {map(this.getProviderForm(), (value, key) => (
<Input <Input
didCheckErrors={this.props.didCheckErrors} didCheckErrors={this.props.didCheckErrors}
errors={get(this.props.formErrors, [findIndex(this.props.formErrors, ['name', key]), 'errors'])} errors={get(this.props.formErrors, [
findIndex(this.props.formErrors, ['name', key]),
'errors',
])}
key={key} key={key}
label={{ id: value.label }} label={{ id: value.label }}
name={key} name={key}

View File

@ -13,22 +13,14 @@ import { findIndex, get, isEmpty } from 'lodash';
// You can find these components in either // You can find these components in either
// ./node_modules/strapi-helper-plugin/lib/src // ./node_modules/strapi-helper-plugin/lib/src
// or strapi/packages/strapi-helper-plugin/lib/src // or strapi/packages/strapi-helper-plugin/lib/src
import ContainerFluid from 'components/ContainerFluid'; import { ContainerFluid, HeaderNav, PluginHeader } from 'strapi-helper-plugin';
import HeaderNav from 'components/HeaderNav';
import PluginHeader from 'components/PluginHeader';
import pluginId from '../../pluginId'; import pluginId from '../../pluginId';
// Plugin's components // Plugin's components
import EditForm from '../../components/EditForm'; import EditForm from '../../components/EditForm';
import { import { getSettings, onCancel, onChange, setErrors, submit } from './actions';
getSettings,
onCancel,
onChange,
setErrors,
submit,
} from './actions';
import reducer from './reducer'; import reducer from './reducer';
import saga from './saga'; import saga from './saga';
@ -47,35 +39,55 @@ class ConfigPage extends React.Component {
// Redirect the user to the email list after modifying is provider // Redirect the user to the email list after modifying is provider
if (prevProps.submitSuccess !== this.props.submitSuccess) { if (prevProps.submitSuccess !== this.props.submitSuccess) {
this.props.history.push(`/plugins/email/configurations/${this.props.match.params.env}`); this.props.history.push(
`/plugins/email/configurations/${this.props.match.params.env}`,
);
} }
} }
getSelectedProviderIndex = () => findIndex(this.props.settings.providers, ['provider', get(this.props.modifiedData, 'provider')]); getSelectedProviderIndex = () =>
findIndex(this.props.settings.providers, [
'provider',
get(this.props.modifiedData, 'provider'),
]);
/** /**
* Get Settings depending on the props * Get Settings depending on the props
* @param {Object} props * @param {Object} props
* @return {Func} calls the saga that gets the current settings * @return {Func} calls the saga that gets the current settings
*/ */
getSettings = (props) => { getSettings = props => {
const { match: { params: { env} } } = props; const {
match: {
params: { env },
},
} = props;
this.props.getSettings(env); this.props.getSettings(env);
} };
generateLinks = () => { generateLinks = () => {
const headerNavLinks = this.props.appEnvironments.reduce((acc, current) => { const headerNavLinks = this.props.appEnvironments
const link = Object.assign(current, { to: `/plugins/email/configurations/${current.name}` }); .reduce((acc, current) => {
acc.push(link); const link = Object.assign(current, {
return acc; to: `/plugins/email/configurations/${current.name}`,
}, []).sort(link => link.name === 'production'); });
acc.push(link);
return acc;
}, [])
.sort(link => link.name === 'production');
return headerNavLinks; return headerNavLinks;
} };
handleSubmit = (e) => { handleSubmit = e => {
e.preventDefault(); e.preventDefault();
const formErrors = Object.keys(get(this.props.settings, ['providers', this.getSelectedProviderIndex(), 'auth'], {})).reduce((acc, current) => { const formErrors = Object.keys(
get(
this.props.settings,
['providers', this.getSelectedProviderIndex(), 'auth'],
{},
),
).reduce((acc, current) => {
if (isEmpty(get(this.props.modifiedData, current, ''))) { if (isEmpty(get(this.props.modifiedData, current, ''))) {
acc.push({ acc.push({
name: current, name: current,
@ -90,7 +102,7 @@ class ConfigPage extends React.Component {
} }
return this.props.submit(); return this.props.submit();
} };
pluginHeaderActions = [ pluginHeaderActions = [
{ {
@ -115,7 +127,7 @@ class ConfigPage extends React.Component {
<PluginHeader <PluginHeader
actions={this.pluginHeaderActions} actions={this.pluginHeaderActions}
description={{ id: 'email.ConfigPage.description' }} description={{ id: 'email.ConfigPage.description' }}
title={{ id: 'email.ConfigPage.title'}} title={{ id: 'email.ConfigPage.title' }}
/> />
<HeaderNav links={this.generateLinks()} /> <HeaderNav links={this.generateLinks()} />
<EditForm <EditForm
@ -174,9 +186,16 @@ function mapDispatchToProps(dispatch) {
const mapStateToProps = selectConfigPage(); const mapStateToProps = selectConfigPage();
const withConnect = connect(mapStateToProps, mapDispatchToProps); const withConnect = connect(
mapStateToProps,
mapDispatchToProps,
);
const withReducer = strapi.injectReducer({ key: 'configPage', reducer, pluginId }); const withReducer = strapi.injectReducer({
key: 'configPage',
reducer,
pluginId,
});
const withSaga = strapi.injectSaga({ key: 'configPage', saga, pluginId }); const withSaga = strapi.injectSaga({ key: 'configPage', saga, pluginId });
export default compose( export default compose(

View File

@ -1,18 +1,9 @@
// import { LOCATION_CHANGE } from 'react-router-redux'; // import { LOCATION_CHANGE } from 'react-router-redux';
import { all, call, fork, put, select, takeLatest } from 'redux-saga/effects'; import { all, call, fork, put, select, takeLatest } from 'redux-saga/effects';
import request from 'utils/request'; import { request } from 'strapi-helper-plugin';
import { import { getSettingsSucceeded, submitSucceeded } from './actions';
getSettingsSucceeded, import { GET_SETTINGS, SUBMIT } from './constants';
submitSucceeded, import { makeSelectEnv, makeSelectModifiedData } from './selectors';
} from './actions';
import {
GET_SETTINGS,
SUBMIT,
} from './constants';
import {
makeSelectEnv,
makeSelectModifiedData,
} from './selectors';
export function* settingsGet(action) { export function* settingsGet(action) {
try { try {
@ -23,7 +14,7 @@ export function* settingsGet(action) {
]); ]);
yield put(getSettingsSucceeded(response[0], response[1].environments)); yield put(getSettingsSucceeded(response[0], response[1].environments));
} catch(err) { } catch (err) {
strapi.notification.error('notification.error'); strapi.notification.error('notification.error');
} }
} }
@ -46,7 +37,7 @@ export function* submit() {
// Update reducer with optimisticResponse // Update reducer with optimisticResponse
strapi.notification.success('email.notification.config.success'); strapi.notification.success('email.notification.config.success');
yield put(submitSucceeded(body)); yield put(submitSucceeded(body));
} catch(err) { } catch (err) {
strapi.notification.error('notification.error'); strapi.notification.error('notification.error');
// TODO handle error PUT // TODO handle error PUT
} }

View File

@ -1,20 +0,0 @@
/**
* NotFoundPage
*
* This is the page we show when the user visits a url that doesn't have a route
*
* NOTE: while this component should technically be a stateless functional
* component (SFC), hot reloading does not currently support SFCs. If hot
* reloading is not a neccessity for you then you can refactor it and remove
* the linting exception.
*/
import React from 'react';
import NotFound from 'components/NotFound';
export default class NotFoundPage extends React.Component {
render() {
return <NotFound {...this.props} />;
}
}

View File

@ -0,0 +1,93 @@
import React from 'react';
import { reduce } from 'lodash';
import pluginPkg from '../../package.json';
import pluginId from './pluginId';
import App from './containers/App';
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
const formatMessages = messages =>
reduce(
messages,
(result, value, key) => {
result[`${pluginId}.${key}`] = value;
return result;
},
{},
);
const requireTranslations = language => {
try {
return require(`./translations/${language}.json`); // eslint-disable-line global-require
} catch (error) {
console.error(
`Unable to load "${language}" translation for the plugin ${pluginId}. Please make sure "${language}.json" file exists in "pluginPath/admin/src/translations" folder.`,
);
return;
}
};
const translationMessages = reduce(
strapi.languages,
(result, language) => {
result[language] = formatMessages(requireTranslations(language));
return result;
},
{},
);
const layout = (() => {
try {
return require('../../config/layout.js'); // eslint-disable-line import/no-unresolved
} catch (err) {
return null;
}
})();
const injectedComponents = (() => {
try {
return require('./injectedComponents').default; // eslint-disable-line import/no-unresolved
} catch (err) {
return [];
}
})();
const initializer = (() => {
try {
return require('./initializer');
} catch (err) {
return null;
}
})();
const lifecycles = (() => {
try {
return require('./lifecycles');
} catch (err) {
return null;
}
})();
function Comp(props) {
return <App {...props} />;
}
const plugin = {
blockerComponent: null,
blockerComponentProps: {},
description: pluginDescription,
icon: pluginPkg.strapi.icon,
id: pluginId,
initializer,
injectedComponents,
layout,
lifecycles,
leftMenuLinks: [],
leftMenuSections: [],
mainComponent: Comp,
name: pluginPkg.strapi.name,
preventComponentRendering: false,
translationMessages,
};
export default plugin;

View File

@ -1,21 +1,21 @@
/** /**
* *
* EditForm * EditForm
* *
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { get } from 'lodash'; import { get } from 'lodash';
import cn from 'classnames'; import cn from 'classnames';
import LoadingIndicator from 'components/LoadingIndicator'; import { InputsIndex as Input, LoadingIndicator } from 'strapi-helper-plugin';
import Input from 'components/InputsIndex';
import styles from './styles.scss'; import styles from './styles.scss';
class EditForm extends React.Component { // eslint-disable-line react/prefer-stateless-function class EditForm extends React.Component {
generateSelectOptions = () => ( // eslint-disable-line react/prefer-stateless-function
generateSelectOptions = () =>
Object.keys(get(this.props.values, 'roles', [])).reduce((acc, current) => { Object.keys(get(this.props.values, 'roles', [])).reduce((acc, current) => {
const option = { const option = {
id: get(this.props.values.roles, [current, 'name']), id: get(this.props.values.roles, [current, 'name']),
@ -23,13 +23,17 @@ class EditForm extends React.Component { // eslint-disable-line react/prefer-sta
}; };
acc.push(option); acc.push(option);
return acc; return acc;
}, []) }, []);
)
render() { render() {
if (this.props.showLoaders) { if (this.props.showLoaders) {
return ( return (
<div className={cn(styles.editForm, this.props.showLoaders && styles.loadIndicatorContainer)}> <div
className={cn(
styles.editForm,
this.props.showLoaders && styles.loadIndicatorContainer,
)}
>
<LoadingIndicator /> <LoadingIndicator />
</div> </div>
); );
@ -39,7 +43,9 @@ class EditForm extends React.Component { // eslint-disable-line react/prefer-sta
<div className={styles.editForm}> <div className={styles.editForm}>
<div className="row"> <div className="row">
<Input <Input
inputDescription={{ id: 'users-permissions.EditForm.inputSelect.description.role' }} inputDescription={{
id: 'users-permissions.EditForm.inputSelect.description.role',
}}
inputClassName={styles.inputStyle} inputClassName={styles.inputStyle}
label={{ id: 'users-permissions.EditForm.inputSelect.label.role' }} label={{ id: 'users-permissions.EditForm.inputSelect.label.role' }}
name="advanced.settings.default_role" name="advanced.settings.default_role"
@ -53,7 +59,9 @@ class EditForm extends React.Component { // eslint-disable-line react/prefer-sta
<div className="row"> <div className="row">
<Input <Input
label={{ id: 'users-permissions.EditForm.inputToggle.label.email' }} label={{ id: 'users-permissions.EditForm.inputToggle.label.email' }}
inputDescription={{ id: 'users-permissions.EditForm.inputToggle.description.email' }} inputDescription={{
id: 'users-permissions.EditForm.inputToggle.description.email',
}}
name="advanced.settings.unique_email" name="advanced.settings.unique_email"
onChange={this.props.onChange} onChange={this.props.onChange}
type="toggle" type="toggle"
@ -89,8 +97,12 @@ class EditForm extends React.Component { // eslint-disable-line react/prefer-sta
*/} */}
<div className="row"> <div className="row">
<Input <Input
label={{ id: 'users-permissions.EditForm.inputToggle.label.sign-up' }} label={{
inputDescription={{ id: 'users-permissions.EditForm.inputToggle.description.sign-up' }} id: 'users-permissions.EditForm.inputToggle.label.sign-up',
}}
inputDescription={{
id: 'users-permissions.EditForm.inputToggle.description.sign-up',
}}
name="advanced.settings.allow_register" name="advanced.settings.allow_register"
onChange={this.props.onChange} onChange={this.props.onChange}
type="toggle" type="toggle"
@ -100,8 +112,14 @@ class EditForm extends React.Component { // eslint-disable-line react/prefer-sta
<div className={styles.separator} /> <div className={styles.separator} />
<div className="row"> <div className="row">
<Input <Input
label={{ id: 'users-permissions.EditForm.inputToggle.label.email-confirmation' }} label={{
inputDescription={{ id: 'users-permissions.EditForm.inputToggle.description.email-confirmation' }} id:
'users-permissions.EditForm.inputToggle.label.email-confirmation',
}}
inputDescription={{
id:
'users-permissions.EditForm.inputToggle.description.email-confirmation',
}}
name="advanced.settings.email_confirmation" name="advanced.settings.email_confirmation"
onChange={this.props.onChange} onChange={this.props.onChange}
type="toggle" type="toggle"
@ -110,12 +128,21 @@ class EditForm extends React.Component { // eslint-disable-line react/prefer-sta
</div> </div>
<div className="row"> <div className="row">
<Input <Input
label={{ id: 'users-permissions.EditForm.inputToggle.label.email-confirmation-redirection' }} label={{
inputDescription={{ id: 'users-permissions.EditForm.inputToggle.description.email-confirmation-redirection' }} id:
'users-permissions.EditForm.inputToggle.label.email-confirmation-redirection',
}}
inputDescription={{
id:
'users-permissions.EditForm.inputToggle.description.email-confirmation-redirection',
}}
name="advanced.settings.email_confirmation_redirection" name="advanced.settings.email_confirmation_redirection"
onChange={this.props.onChange} onChange={this.props.onChange}
type="text" type="text"
value={get(this.props.values.settings, 'email_confirmation_redirection')} value={get(
this.props.values.settings,
'email_confirmation_redirection',
)}
/> />
</div> </div>
</div> </div>

View File

@ -1,8 +1,8 @@
/** /**
* *
* InputSearchContainer * InputSearchContainer
* *
*/ */
import React from 'react'; import React from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
@ -10,12 +10,13 @@ import { findIndex, has, includes, isEmpty, map, toLower } from 'lodash';
import cn from 'classnames'; import cn from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Label from 'components/Label'; import { Label } from 'strapi-helper-plugin';
import InputSearchLi from '../InputSearchLi'; import InputSearchLi from '../InputSearchLi';
import styles from './styles.scss'; import styles from './styles.scss';
class InputSearchContainer extends React.Component { // eslint-disable-line react/prefer-stateless-function class InputSearchContainer extends React.Component {
// eslint-disable-line react/prefer-stateless-function
state = { state = {
errors: [], errors: [],
filteredUsers: this.props.values, filteredUsers: this.props.values,
@ -27,11 +28,17 @@ class InputSearchContainer extends React.Component { // eslint-disable-line reac
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (nextProps.didDeleteUser !== this.props.didDeleteUser) { if (nextProps.didDeleteUser !== this.props.didDeleteUser) {
this.setState({ users: nextProps.values, filteredUsers: nextProps.values }); this.setState({
users: nextProps.values,
filteredUsers: nextProps.values,
});
} }
if (nextProps.didGetUsers !== this.props.didGetUsers) { if (nextProps.didGetUsers !== this.props.didGetUsers) {
this.setState({ users: nextProps.values, filteredUsers: nextProps.values }); this.setState({
users: nextProps.values,
filteredUsers: nextProps.values,
});
} }
if (nextProps.didFetchUsers !== this.props.didFetchUsers) { if (nextProps.didFetchUsers !== this.props.didFetchUsers) {
@ -42,24 +49,31 @@ class InputSearchContainer extends React.Component { // eslint-disable-line reac
handleBlur = () => this.setState({ isFocused: !this.state.isFocused }); handleBlur = () => this.setState({ isFocused: !this.state.isFocused });
handleChange = ({ target }) => { handleChange = ({ target }) => {
const filteredUsers = isEmpty(target.value) ? const filteredUsers = isEmpty(target.value)
this.state.users ? this.state.users
: this.state.users.filter((user) => includes(toLower(user.name), toLower(target.value))); : this.state.users.filter(user =>
includes(toLower(user.name), toLower(target.value)),
);
if (isEmpty(filteredUsers) && !isEmpty(target.value)) { if (isEmpty(filteredUsers) && !isEmpty(target.value)) {
this.props.getUser(target.value); this.props.getUser(target.value);
} }
if (isEmpty(target.value)) { if (isEmpty(target.value)) {
return this.setState({ value: target.value, isAdding: false, users: this.props.values, filteredUsers: this.props.values }); return this.setState({
value: target.value,
isAdding: false,
users: this.props.values,
filteredUsers: this.props.values,
});
} }
this.setState({ value: target.value, filteredUsers }); this.setState({ value: target.value, filteredUsers });
} };
handleFocus = () => this.setState({ isFocused: !this.state.isFocused }); handleFocus = () => this.setState({ isFocused: !this.state.isFocused });
handleClick = (item) => { handleClick = item => {
if (this.state.isAdding) { if (this.state.isAdding) {
const id = has(item, '_id') ? '_id' : 'id'; const id = has(item, '_id') ? '_id' : 'id';
const users = this.props.values; const users = this.props.values;
@ -72,22 +86,36 @@ class InputSearchContainer extends React.Component { // eslint-disable-line reac
// Reset the input focus // Reset the input focus
this.searchInput.focus(); this.searchInput.focus();
// Empty the input and display users // Empty the input and display users
this.setState({ value: '', isAdding: false, users, filteredUsers: users }); this.setState({
value: '',
isAdding: false,
users,
filteredUsers: users,
});
} else { } else {
this.props.onClickDelete(item); this.props.onClickDelete(item);
} }
} };
render() { render() {
return ( return (
<div className={cn(styles.inputSearch, 'col-md-6')}> <div className={cn(styles.inputSearch, 'col-md-6')}>
<Label htmlFor={this.props.name} message={this.props.label} /> <Label htmlFor={this.props.name} message={this.props.label} />
<div className={cn('input-group')}> <div className={cn('input-group')}>
<span className={cn('input-group-addon', styles.addon, this.state.isFocused && styles.addonFocus,)} /> <span
className={cn(
'input-group-addon',
styles.addon,
this.state.isFocused && styles.addonFocus,
)}
/>
<FormattedMessage id="users-permissions.InputSearch.placeholder"> <FormattedMessage id="users-permissions.InputSearch.placeholder">
{(message) => ( {message => (
<input <input
className={cn('form-control', !isEmpty(this.state.errors) ? 'is-invalid': '')} className={cn(
'form-control',
!isEmpty(this.state.errors) ? 'is-invalid' : '',
)}
id={this.props.name} id={this.props.name}
name={this.props.name} name={this.props.name}
onBlur={this.handleBlur} onBlur={this.handleBlur}
@ -96,14 +124,21 @@ class InputSearchContainer extends React.Component { // eslint-disable-line reac
value={this.state.value} value={this.state.value}
placeholder={message} placeholder={message}
type="text" type="text"
ref={(input) => { this.searchInput = input; }} ref={input => {
this.searchInput = input;
}}
/> />
)} )}
</FormattedMessage> </FormattedMessage>
</div> </div>
<div className={cn(styles.ulContainer, this.state.isFocused && styles.ulFocused)}> <div
className={cn(
styles.ulContainer,
this.state.isFocused && styles.ulFocused,
)}
>
<ul> <ul>
{map(this.state.filteredUsers, (user) => ( {map(this.state.filteredUsers, user => (
<InputSearchLi <InputSearchLi
key={user.id || user._id} key={user.id || user._id}
item={user} item={user}

View File

@ -1,8 +1,8 @@
/** /**
* *
* List * List
* *
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -11,11 +11,8 @@ import { map, omitBy, size } from 'lodash';
import cn from 'classnames'; import cn from 'classnames';
// Components from strapi-helper-plugin // Components from strapi-helper-plugin
import LoadingBar from 'components/LoadingBar'; import { Button, LoadingBar, LoadingIndicator } from 'strapi-helper-plugin';
import LoadingIndicator from 'components/LoadingIndicator';
// Design
import Button from 'components/Button';
import ListRow from '../ListRow'; import ListRow from '../ListRow';
import styles from './styles.scss'; import styles from './styles.scss';
@ -23,57 +20,117 @@ import styles from './styles.scss';
const generateListTitle = (data, settingType) => { const generateListTitle = (data, settingType) => {
switch (settingType) { switch (settingType) {
case 'roles': { case 'roles': {
const title = size(data) < 2 ? const title =
<FormattedMessage id="users-permissions.List.title.roles.singular" values={{ number: size(data) }} /> size(data) < 2 ? (
: <FormattedMessage id="users-permissions.List.title.roles.plural" values={{ number: size(data) }} />; <FormattedMessage
id="users-permissions.List.title.roles.singular"
values={{ number: size(data) }}
/>
) : (
<FormattedMessage
id="users-permissions.List.title.roles.plural"
values={{ number: size(data) }}
/>
);
return title; return title;
} }
case 'providers': { case 'providers': {
const enabledProvidersSize = data.filter(o => o.enabled).length; const enabledProvidersSize = data.filter(o => o.enabled).length;
const enabledProviders = enabledProvidersSize > 1 ? const enabledProviders =
<FormattedMessage id="users-permissions.List.title.providers.enabled.plural" values={{ number: enabledProvidersSize }} /> enabledProvidersSize > 1 ? (
: <FormattedMessage id="users-permissions.List.title.providers.enabled.singular" values={{ number: enabledProvidersSize }} />; <FormattedMessage
id="users-permissions.List.title.providers.enabled.plural"
values={{ number: enabledProvidersSize }}
/>
) : (
<FormattedMessage
id="users-permissions.List.title.providers.enabled.singular"
values={{ number: enabledProvidersSize }}
/>
);
const disabledProviders = size(data) - enabledProvidersSize > 1 ? const disabledProviders =
<FormattedMessage id="users-permissions.List.title.providers.disabled.plural" values={{ number: size(data) - enabledProvidersSize }} /> size(data) - enabledProvidersSize > 1 ? (
: <FormattedMessage id="users-permissions.List.title.providers.disabled.singular" values={{ number: size(data) - enabledProvidersSize }} />; <FormattedMessage
id="users-permissions.List.title.providers.disabled.plural"
return <div>{enabledProviders}&nbsp;{disabledProviders}</div>; values={{ number: size(data) - enabledProvidersSize }}
/>
) : (
<FormattedMessage
id="users-permissions.List.title.providers.disabled.singular"
values={{ number: size(data) - enabledProvidersSize }}
/>
);
return (
<div>
{enabledProviders}&nbsp;{disabledProviders}
</div>
);
} }
case 'email-templates': { case 'email-templates': {
return size(data) > 1 ? return size(data) > 1 ? (
<FormattedMessage id="users-permissions.List.title.emailTemplates.plural" values={{ number: size(data) }} /> <FormattedMessage
: <FormattedMessage id="users-permissions.List.title.emailTemplates.singular" values={{ number: size(data) }} />; id="users-permissions.List.title.emailTemplates.plural"
values={{ number: size(data) }}
/>
) : (
<FormattedMessage
id="users-permissions.List.title.emailTemplates.singular"
values={{ number: size(data) }}
/>
);
} }
default: default:
return ''; return '';
} }
}; };
function List({ data, deleteData, noButton, onButtonClick, settingType, showLoaders, values }) { function List({
const object = omitBy(data, (v) => v.name === 'server'); // Remove the server key when displaying providers data,
deleteData,
noButton,
onButtonClick,
settingType,
showLoaders,
values,
}) {
const object = omitBy(data, v => v.name === 'server'); // Remove the server key when displaying providers
return ( return (
<div className={styles.list}> <div className={styles.list}>
<div className={styles.flex}> <div className={styles.flex}>
<div className={styles.titleContainer}> <div className={styles.titleContainer}>
{showLoaders ? <LoadingBar style={{ marginTop: '0' }} /> : generateListTitle(data, settingType)} {showLoaders ? (
<LoadingBar style={{ marginTop: '0' }} />
) : (
generateListTitle(data, settingType)
)}
</div> </div>
<div className={styles.buttonContainer}> <div className={styles.buttonContainer}>
{noButton ? ( {noButton ? (
'' ''
) : ( ) : (
<Button onClick={onButtonClick} secondaryHotlineAdd> <Button onClick={onButtonClick} secondaryHotlineAdd>
<FormattedMessage id={`users-permissions.List.button.${settingType}`} /> <FormattedMessage
id={`users-permissions.List.button.${settingType}`}
/>
</Button> </Button>
)} )}
</div> </div>
</div> </div>
<div className={cn(styles.ulContainer, showLoaders && styles.loadingContainer, showLoaders && settingType === 'roles' && styles.loadingContainerRole )}> <div
{showLoaders ? <LoadingIndicator /> : ( className={cn(
styles.ulContainer,
showLoaders && styles.loadingContainer,
showLoaders && settingType === 'roles' && styles.loadingContainerRole,
)}
>
{showLoaders ? (
<LoadingIndicator />
) : (
<ul className={noButton ? styles.listPadded : ''}> <ul className={noButton ? styles.listPadded : ''}>
{map(object, item => ( {map(object, item => (
<ListRow <ListRow

View File

@ -11,8 +11,7 @@ import { FormattedMessage } from 'react-intl';
import { capitalize, get, includes } from 'lodash'; import { capitalize, get, includes } from 'lodash';
// Design // Design
import IcoContainer from 'components/IcoContainer'; import { IcoContainer, PopUpWarning } from 'strapi-helper-plugin';
import PopUpWarning from 'components/PopUpWarning';
import en from '../../translations/en.json'; import en from '../../translations/en.json';
import styles from './styles.scss'; import styles from './styles.scss';
@ -88,10 +87,10 @@ class ListRow extends React.Component {
get(this.props.item, 'name'), get(this.props.item, 'name'),
'enabled', 'enabled',
]) ? ( ]) ? (
<span style={{ color: '#5A9E06' }}>Enabled</span> <span style={{ color: '#5A9E06' }}>Enabled</span>
) : ( ) : (
<span style={{ color: '#F64D0A' }}>Disabled</span> <span style={{ color: '#F64D0A' }}>Disabled</span>
)} )}
</div> </div>
<div className="col-md-2"> <div className="col-md-2">
<IcoContainer icons={icons} /> <IcoContainer icons={icons} />
@ -185,7 +184,7 @@ ListRow.contextTypes = {
ListRow.defaultProps = { ListRow.defaultProps = {
item: { item: {
name: 'Owner', name: 'Owner',
description: "Rule them all. This role can't be deleted", description: 'Rule them all. This role can\'t be deleted',
nb_users: 1, nb_users: 1,
icon: 'envelope', icon: 'envelope',
}, },

View File

@ -1,8 +1,8 @@
/** /**
* *
* Policies * Policies
* *
*/ */
import React from 'react'; import React from 'react';
import cn from 'classnames'; import cn from 'classnames';
@ -10,24 +10,42 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { get, isEmpty, map, takeRight, toLower, without } from 'lodash'; import { get, isEmpty, map, takeRight, toLower, without } from 'lodash';
import Input from 'components/InputsIndex'; import { InputsIndex as Input } from 'strapi-helper-plugin';
import BoundRoute from '../BoundRoute'; import BoundRoute from '../BoundRoute';
import styles from './styles.scss'; import styles from './styles.scss';
class Policies extends React.Component { // eslint-disable-line react/prefer-stateless-function class Policies extends React.Component {
handleChange = (e) => this.context.onChange(e); // eslint-disable-line react/prefer-stateless-function
handleChange = e => this.context.onChange(e);
render() { render() {
const baseTitle = 'users-permissions.Policies.header'; const baseTitle = 'users-permissions.Policies.header';
const title = this.props.shouldDisplayPoliciesHint ? 'hint' : 'title'; const title = this.props.shouldDisplayPoliciesHint ? 'hint' : 'title';
const value = get(this.props.values, this.props.inputSelectName); const value = get(this.props.values, this.props.inputSelectName);
const path = without(this.props.inputSelectName.split('.'), 'permissions', 'controllers', 'policy'); const path = without(
const controllerRoutes = get(this.props.routes, without(this.props.inputSelectName.split('.'), 'permissions', 'controllers', 'policy')[0]); this.props.inputSelectName.split('.'),
const routes = isEmpty(controllerRoutes) ? [] : controllerRoutes.filter(o => toLower(o.handler) === toLower(takeRight(path, 2).join('.'))); 'permissions',
'controllers',
'policy',
);
const controllerRoutes = get(
this.props.routes,
without(
this.props.inputSelectName.split('.'),
'permissions',
'controllers',
'policy',
)[0],
);
const routes = isEmpty(controllerRoutes)
? []
: controllerRoutes.filter(
o => toLower(o.handler) === toLower(takeRight(path, 2).join('.')),
);
return ( return (
<div className={cn('col-md-5',styles.policies)}> <div className={cn('col-md-5', styles.policies)}>
<div className="container-fluid"> <div className="container-fluid">
<div className={cn('row', styles.inputWrapper)}> <div className={cn('row', styles.inputWrapper)}>
<div className={cn('col-md-12', styles.header)}> <div className={cn('col-md-12', styles.header)}>
@ -44,12 +62,16 @@ class Policies extends React.Component { // eslint-disable-line react/prefer-sta
validations={{}} validations={{}}
value={value} value={value}
/> />
) : ''} ) : (
''
)}
</div> </div>
<div className="row"> <div className="row">
{!this.props.shouldDisplayPoliciesHint ? ( {!this.props.shouldDisplayPoliciesHint
map(routes, (route, key) => <BoundRoute key={key} route={route} />) ? map(routes, (route, key) => (
) : ''} <BoundRoute key={key} route={route} />
))
: ''}
</div> </div>
</div> </div>
</div> </div>

View File

@ -23,7 +23,7 @@ import {
takeRight, takeRight,
} from 'lodash'; } from 'lodash';
import Input from 'components/InputsIndex'; import { InputsIndex as Input } from 'strapi-helper-plugin';
// Translations // Translations
import en from '../../translations/en.json'; import en from '../../translations/en.json';

View File

@ -33,13 +33,21 @@ class App extends React.Component {
return ( return (
<div className={pluginId}> <div className={pluginId}>
<Switch> <Switch>
<Route path={`/plugins/${pluginId}/auth/:authType/:id?`} component={AuthPage} exact /> <Route
path={`/plugins/${pluginId}/auth/:authType/:id?`}
component={AuthPage}
exact
/>
<Route <Route
path={`/plugins/${pluginId}/:settingType/:actionType/:id?`} path={`/plugins/${pluginId}/:settingType/:actionType/:id?`}
component={EditPage} component={EditPage}
exact exact
/> />
<Route path={`/plugins/${pluginId}/:settingType`} component={HomePage} exact /> <Route
path={`/plugins/${pluginId}/:settingType`}
component={HomePage}
exact
/>
<Route component={NotFoundPage} /> <Route component={NotFoundPage} />
</Switch> </Switch>
</div> </div>

View File

@ -14,11 +14,7 @@ import { findIndex, get, isBoolean, isEmpty, map, replace } from 'lodash';
import cn from 'classnames'; import cn from 'classnames';
// Design // Design
import Button from 'components/Button'; import { auth, Button, InputsIndex as Input } from 'strapi-helper-plugin';
import Input from 'components/InputsIndex';
// Utils
import auth from 'utils/auth';
import pluginId from '../../pluginId'; import pluginId from '../../pluginId';
@ -39,20 +35,19 @@ import makeSelectAuthPage from './selectors';
import styles from './styles.scss'; import styles from './styles.scss';
export class AuthPage extends React.Component { // eslint-disable-line react/prefer-stateless-function export class AuthPage extends React.Component {
// eslint-disable-line react/prefer-stateless-function
componentDidMount() { componentDidMount() {
auth.clearAppStorage(); auth.clearAppStorage();
this.setForm(); this.setForm();
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const { const {
hideLoginErrorsInput, hideLoginErrorsInput,
match: { match: {
params : { params: { authType },
authType, },
},
},
submitSuccess, submitSuccess,
} = this.props; } = this.props;
@ -64,7 +59,7 @@ export class AuthPage extends React.Component { // eslint-disable-line react/pre
if (submitSuccess) { if (submitSuccess) {
switch (authType) { switch (authType) {
case 'login': case 'login':
case 'reset-password': case 'reset-password':
// Check if we have token to handle redirection to login or admin. // Check if we have token to handle redirection to login or admin.
// Done to prevent redirection to admin after reset password if user should // Done to prevent redirection to admin after reset password if user should
// not have access. // not have access.
@ -86,46 +81,71 @@ export class AuthPage extends React.Component { // eslint-disable-line react/pre
getFormErrors = () => { getFormErrors = () => {
const { formErrors } = this.props; const { formErrors } = this.props;
return get(formErrors, ['0', 'errors', '0', 'id']); return get(formErrors, ['0', 'errors', '0', 'id']);
} };
setForm = () => { setForm = () => {
const { const {
location: { location: { search },
search,
},
match: { match: {
params: { params: { authType, id },
authType,
id,
},
}, },
setForm, setForm,
} = this.props; } = this.props;
const params = search ? replace(search, '?code=', '') : id; const params = search ? replace(search, '?code=', '') : id;
setForm(authType, params); setForm(authType, params);
} };
isAuthType = type => { isAuthType = type => {
const { match: { params: { authType } } } = this.props; const {
match: {
params: { authType },
},
} = this.props;
return authType === type; return authType === type;
} };
handleSubmit = (e) => { handleSubmit = e => {
const { modifiedData, setErrors, submit } = this.props; const { modifiedData, setErrors, submit } = this.props;
e.preventDefault(); e.preventDefault();
const formErrors = Object.keys(modifiedData).reduce((acc, key) => { const formErrors = Object.keys(modifiedData).reduce((acc, key) => {
if (isEmpty(get(modifiedData, key)) && !isBoolean(get(modifiedData, key))) { if (
acc.push({ name: key, errors: [{ id: 'components.Input.error.validation.required' }] }); isEmpty(get(modifiedData, key)) &&
!isBoolean(get(modifiedData, key))
) {
acc.push({
name: key,
errors: [{ id: 'components.Input.error.validation.required' }],
});
} }
if (!isEmpty(get(modifiedData, 'password')) && !isEmpty(get(modifiedData, 'confirmPassword')) && findIndex(acc, ['name', 'confirmPassword']) === -1) { if (
!isEmpty(get(modifiedData, 'password')) &&
!isEmpty(get(modifiedData, 'confirmPassword')) &&
findIndex(acc, ['name', 'confirmPassword']) === -1
) {
if (modifiedData.password.length < 6) { if (modifiedData.password.length < 6) {
acc.push({ name: 'password', errors: [{ id: 'users-permissions.components.Input.error.password.length' }] }); acc.push({
name: 'password',
errors: [
{
id: 'users-permissions.components.Input.error.password.length',
},
],
});
} }
if (get(modifiedData, 'password') !== get(modifiedData, 'confirmPassword')) { if (
acc.push({ name: 'confirmPassword', errors: [{ id: 'users-permissions.components.Input.error.password.noMatch' }] }); get(modifiedData, 'password') !== get(modifiedData, 'confirmPassword')
) {
acc.push({
name: 'confirmPassword',
errors: [
{
id: 'users-permissions.components.Input.error.password.noMatch',
},
],
});
} }
} }
@ -137,23 +157,35 @@ export class AuthPage extends React.Component { // eslint-disable-line react/pre
if (isEmpty(formErrors)) { if (isEmpty(formErrors)) {
submit(this.context); submit(this.context);
} }
} };
redirect = path => this.props.history.push(path); redirect = path => this.props.history.push(path);
renderButton = () => { renderButton = () => {
const { match: { params: { authType } }, submitSuccess } = this.props; const {
match: {
params: { authType },
},
submitSuccess,
} = this.props;
if (this.isAuthType('login')) { if (this.isAuthType('login')) {
return ( return (
<div className={cn('col-md-6', styles.loginButton)}> <div className={cn('col-md-6', styles.loginButton)}>
<Button primary label="users-permissions.Auth.form.button.login" type="submit" /> <Button
primary
label="users-permissions.Auth.form.button.login"
type="submit"
/>
</div> </div>
); );
} }
const isEmailForgotSent = this.isAuthType('forgot-password') && submitSuccess; const isEmailForgotSent =
const label = isEmailForgotSent ? 'users-permissions.Auth.form.button.forgot-password.success' : `users-permissions.Auth.form.button.${authType}`; this.isAuthType('forgot-password') && submitSuccess;
const label = isEmailForgotSent
? 'users-permissions.Auth.form.button.forgot-password.success'
: `users-permissions.Auth.form.button.${authType}`;
return ( return (
<div className={cn('col-md-12', styles.buttonContainer)}> <div className={cn('col-md-12', styles.buttonContainer)}>
<Button <Button
@ -165,10 +197,15 @@ export class AuthPage extends React.Component { // eslint-disable-line react/pre
/> />
</div> </div>
); );
} };
renderLogo = () =>
this.isAuthType('register') && (
<div className={styles.logoContainer}>
<img src={LogoStrapi} alt="logo" />
</div>
);
renderLogo = () => this.isAuthType('register') && <div className={styles.logoContainer}><img src={LogoStrapi} alt="logo" /></div>;
renderLink = () => { renderLink = () => {
if (this.isAuthType('login')) { if (this.isAuthType('login')) {
return ( return (
@ -178,7 +215,10 @@ export class AuthPage extends React.Component { // eslint-disable-line react/pre
); );
} }
if (this.isAuthType('forgot-password') || this.isAuthType('register-success')) { if (
this.isAuthType('forgot-password') ||
this.isAuthType('register-success')
) {
return ( return (
<Link to="/plugins/users-permissions/auth/login"> <Link to="/plugins/users-permissions/auth/login">
<FormattedMessage id="users-permissions.Auth.link.ready" /> <FormattedMessage id="users-permissions.Auth.link.ready" />
@ -187,37 +227,41 @@ export class AuthPage extends React.Component { // eslint-disable-line react/pre
} }
return <div />; return <div />;
} };
renderInputs = () => { renderInputs = () => {
const { const {
didCheckErrors, didCheckErrors,
formErrors, formErrors,
match: { match: {
params: { params: { authType },
authType,
},
}, },
modifiedData, modifiedData,
noErrorsDescription, noErrorsDescription,
onChangeInput, onChangeInput,
submitSuccess, submitSuccess,
} = this.props; } = this.props;
const inputs = get(form, ['form', authType]); const inputs = get(form, ['form', authType]);
const isForgotEmailSent = this.isAuthType('forgot-password') && submitSuccess; const isForgotEmailSent =
this.isAuthType('forgot-password') && submitSuccess;
return map(inputs, (input, key) => { return map(inputs, (input, key) => {
const label = const label = isForgotEmailSent
isForgotEmailSent ? {
? { id: 'users-permissions.Auth.form.forgot-password.email.label.success' } id:
: get(input, 'label'); 'users-permissions.Auth.form.forgot-password.email.label.success',
}
: get(input, 'label');
return ( return (
<Input <Input
autoFocus={key === 0} autoFocus={key === 0}
customBootstrapClass={get(input, 'customBootstrapClass')} customBootstrapClass={get(input, 'customBootstrapClass')}
didCheckErrors={didCheckErrors} didCheckErrors={didCheckErrors}
errors={get(formErrors, [findIndex(formErrors, ['name', input.name]), 'errors'])} errors={get(formErrors, [
findIndex(formErrors, ['name', input.name]),
'errors',
])}
key={get(input, 'name')} key={get(input, 'name')}
label={label} label={label}
name={get(input, 'name')} name={get(input, 'name')}
@ -230,11 +274,13 @@ export class AuthPage extends React.Component { // eslint-disable-line react/pre
/> />
); );
}); });
} };
render() { render() {
const { modifiedData, noErrorsDescription, submitSuccess } = this.props; const { modifiedData, noErrorsDescription, submitSuccess } = this.props;
let divStyle = this.isAuthType('register') ? { marginTop: '3.2rem' } : { marginTop: '.9rem' }; let divStyle = this.isAuthType('register')
? { marginTop: '3.2rem' }
: { marginTop: '.9rem' };
if (this.isAuthType('forgot-password') && submitSuccess) { if (this.isAuthType('forgot-password') && submitSuccess) {
divStyle = { marginTop: '.9rem', minHeight: '18.2rem' }; divStyle = { marginTop: '.9rem', minHeight: '18.2rem' };
@ -251,26 +297,32 @@ export class AuthPage extends React.Component { // eslint-disable-line react/pre
)} )}
</div> </div>
<div className={styles.headerDescription}> <div className={styles.headerDescription}>
{this.isAuthType('register') && <FormattedMessage id="users-permissions.Auth.header.register.description" />} {this.isAuthType('register') && (
<FormattedMessage id="users-permissions.Auth.header.register.description" />
)}
</div> </div>
<div <div
className={cn( className={cn(
styles.formContainer, styles.formContainer,
this.isAuthType('forgot-password') && submitSuccess ? styles.borderedSuccess : styles.bordered, this.isAuthType('forgot-password') && submitSuccess
? styles.borderedSuccess
: styles.bordered,
)} )}
style={divStyle} style={divStyle}
> >
<form onSubmit={this.handleSubmit}> <form onSubmit={this.handleSubmit}>
<div className="container-fluid"> <div className="container-fluid">
{noErrorsDescription && !isEmpty(this.getFormErrors())? ( {noErrorsDescription && !isEmpty(this.getFormErrors()) ? (
<div className={styles.errorsContainer}> <div className={styles.errorsContainer}>
<FormattedMessage id={this.getFormErrors()} /> <FormattedMessage id={this.getFormErrors()} />
</div> </div>
): ''} ) : (
''
)}
<div className="row" style={{ textAlign: 'start' }}> <div className="row" style={{ textAlign: 'start' }}>
{!submitSuccess && this.renderInputs()} {!submitSuccess && this.renderInputs()}
{ this.isAuthType('forgot-password') && submitSuccess && ( {this.isAuthType('forgot-password') && submitSuccess && (
<div className={styles.forgotSuccess}> <div className={styles.forgotSuccess}>
<FormattedMessage id="users-permissions.Auth.form.forgot-password.email.label.success" /> <FormattedMessage id="users-permissions.Auth.form.forgot-password.email.label.success" />
<br /> <br />
@ -282,9 +334,7 @@ export class AuthPage extends React.Component { // eslint-disable-line react/pre
</div> </div>
</form> </form>
</div> </div>
<div className={styles.linkContainer}> <div className={styles.linkContainer}>{this.renderLink()}</div>
{this.renderLink()}
</div>
</div> </div>
{this.renderLogo()} {this.renderLogo()}
</div> </div>
@ -323,13 +373,20 @@ function mapDispatchToProps(dispatch) {
setForm, setForm,
submit, submit,
}, },
dispatch dispatch,
); );
} }
const withConnect = connect(mapStateToProps, mapDispatchToProps); const withConnect = connect(
const withReducer = strapi.injectReducer({ key: 'authPage', reducer, pluginId }); mapStateToProps,
const withSaga = strapi.injectSaga({ key: 'authPage', saga, pluginId }); mapDispatchToProps,
);
const withReducer = window.strapi.injectReducer({
key: 'authPage',
reducer,
pluginId,
});
const withSaga = window.strapi.injectSaga({ key: 'authPage', saga, pluginId });
export default compose( export default compose(
withReducer, withReducer,

View File

@ -1,7 +1,6 @@
import { get, includes, isArray, omit, set } from 'lodash'; import { get, includes, isArray, omit, set } from 'lodash';
import { call, fork, put, select, takeLatest } from 'redux-saga/effects'; import { call, fork, put, select, takeLatest } from 'redux-saga/effects';
import auth from 'utils/auth'; import { auth, request } from 'strapi-helper-plugin';
import request from 'utils/request';
import { updateHasAdmin } from '../Initializer/actions'; import { updateHasAdmin } from '../Initializer/actions';

View File

@ -14,11 +14,13 @@ import { findIndex, get, isEmpty, isEqual, size } from 'lodash';
import cn from 'classnames'; import cn from 'classnames';
// Design // Design
import BackHeader from 'components/BackHeader'; import {
import Input from 'components/InputsIndex'; BackHeader,
import LoadingIndicator from 'components/LoadingIndicator'; InputsIndex as Input,
import LoadingIndicatorPage from 'components/LoadingIndicatorPage'; LoadingIndicator,
import PluginHeader from 'components/PluginHeader'; LoadingIndicatorPage,
PluginHeader,
} from 'strapi-helper-plugin';
import InputSearch from '../../components/InputSearchContainer'; import InputSearch from '../../components/InputSearchContainer';
import Plugins from '../../components/Plugins'; import Plugins from '../../components/Plugins';
@ -56,16 +58,15 @@ import saga from './saga';
import styles from './styles.scss'; import styles from './styles.scss';
export class EditPage extends React.Component { // eslint-disable-line react/prefer-stateless-function export class EditPage extends React.Component {
getChildContext = () => ( // eslint-disable-line react/prefer-stateless-function
{ getChildContext = () => ({
onChange: this.props.onChangeInput, onChange: this.props.onChangeInput,
selectAllActions: this.props.selectAllActions, selectAllActions: this.props.selectAllActions,
setInputPoliciesPath: this.props.setInputPoliciesPath, setInputPoliciesPath: this.props.setInputPoliciesPath,
setShouldDisplayPolicieshint: this.props.setShouldDisplayPolicieshint, setShouldDisplayPolicieshint: this.props.setShouldDisplayPolicieshint,
resetShouldDisplayPoliciesHint: this.props.resetShouldDisplayPoliciesHint, resetShouldDisplayPoliciesHint: this.props.resetShouldDisplayPoliciesHint,
} });
);
componentDidMount() { componentDidMount() {
this.props.setActionType(this.props.match.params.actionType); this.props.setActionType(this.props.match.params.actionType);
@ -100,23 +101,35 @@ export class EditPage extends React.Component { // eslint-disable-line react/pre
handleSubmit = () => { handleSubmit = () => {
// Check if the name field is filled // Check if the name field is filled
if (isEmpty(get(this.props.editPage, ['modifiedData', 'name']))) { if (isEmpty(get(this.props.editPage, ['modifiedData', 'name']))) {
return this.props.setErrors([{ name: 'name', errors: [{ id: 'users-permissions.EditPage.form.roles.name.error' }] }]); return this.props.setErrors([
{
name: 'name',
errors: [{ id: 'users-permissions.EditPage.form.roles.name.error' }],
},
]);
} }
this.props.submit(this.context); this.props.submit(this.context);
} };
showLoaderForm = () => { showLoaderForm = () => {
const { editPage: { modifiedData }, match: { params: { actionType } } } = this.props; const {
editPage: { modifiedData },
match: {
params: { actionType },
},
} = this.props;
return actionType !== 'create' && isEmpty(modifiedData); return actionType !== 'create' && isEmpty(modifiedData);
} };
showLoaderPermissions = () => { showLoaderPermissions = () => {
const { editPage: { modifiedData } } = this.props; const {
editPage: { modifiedData },
} = this.props;
return isEmpty(get(modifiedData, ['permissions'])); return isEmpty(get(modifiedData, ['permissions']));
} };
renderFirstBlock = () => ( renderFirstBlock = () => (
<React.Fragment> <React.Fragment>
@ -125,7 +138,11 @@ export class EditPage extends React.Component { // eslint-disable-line react/pre
<Input <Input
autoFocus autoFocus
customBootstrapClass="col-md-12" customBootstrapClass="col-md-12"
errors={get(this.props.editPage, ['formErrors', findIndex(this.props.editPage.formErrors, ['name', 'name']), 'errors'])} errors={get(this.props.editPage, [
'formErrors',
findIndex(this.props.editPage.formErrors, ['name', 'name']),
'errors',
])}
didCheckErrors={this.props.editPage.didCheckErrors} didCheckErrors={this.props.editPage.didCheckErrors}
label={{ id: 'users-permissions.EditPage.form.roles.label.name' }} label={{ id: 'users-permissions.EditPage.form.roles.label.name' }}
name="name" name="name"
@ -138,7 +155,9 @@ export class EditPage extends React.Component { // eslint-disable-line react/pre
<div className="row"> <div className="row">
<Input <Input
customBootstrapClass="col-md-12" customBootstrapClass="col-md-12"
label={{ id: 'users-permissions.EditPage.form.roles.label.description' }} label={{
id: 'users-permissions.EditPage.form.roles.label.description',
}}
name="description" name="description"
onChange={this.props.onChangeInput} onChange={this.props.onChangeInput}
type="textarea" type="textarea"
@ -174,15 +193,17 @@ export class EditPage extends React.Component { // eslint-disable-line react/pre
<div className={styles.separator} /> <div className={styles.separator} />
</div> </div>
</React.Fragment> </React.Fragment>
) );
render() { render() {
const pluginHeaderTitle = this.props.match.params.actionType === 'create' ? const pluginHeaderTitle =
'users-permissions.EditPage.header.title.create' this.props.match.params.actionType === 'create'
: 'users-permissions.EditPage.header.title'; ? 'users-permissions.EditPage.header.title.create'
const pluginHeaderDescription = this.props.match.params.actionType === 'create' ? : 'users-permissions.EditPage.header.title';
'users-permissions.EditPage.header.description.create' const pluginHeaderDescription =
: 'users-permissions.EditPage.header.description'; this.props.match.params.actionType === 'create'
? 'users-permissions.EditPage.header.description.create'
: 'users-permissions.EditPage.header.description';
const pluginHeaderActions = [ const pluginHeaderActions = [
{ {
label: 'users-permissions.EditPage.cancel', label: 'users-permissions.EditPage.cancel',
@ -195,10 +216,13 @@ export class EditPage extends React.Component { // eslint-disable-line react/pre
label: 'users-permissions.EditPage.submit', label: 'users-permissions.EditPage.submit',
onClick: this.handleSubmit, onClick: this.handleSubmit,
type: 'submit', type: 'submit',
disabled: isEqual(this.props.editPage.modifiedData, this.props.editPage.initialData), disabled: isEqual(
this.props.editPage.modifiedData,
this.props.editPage.initialData,
),
}, },
]; ];
if (this.showLoaderForm()) { if (this.showLoaderForm()) {
return <LoadingIndicatorPage />; return <LoadingIndicatorPage />;
} }
@ -217,7 +241,8 @@ export class EditPage extends React.Component { // eslint-disable-line react/pre
description={{ description={{
id: pluginHeaderDescription, id: pluginHeaderDescription,
values: { values: {
description: get(this.props.editPage.initialData, 'description') || '', description:
get(this.props.editPage.initialData, 'description') || '',
}, },
}} }}
actions={pluginHeaderActions} actions={pluginHeaderActions}
@ -231,22 +256,34 @@ export class EditPage extends React.Component { // eslint-disable-line react/pre
<form className={styles.form}> <form className={styles.form}>
<div className="row"> <div className="row">
{this.showLoaderForm() ? ( {this.showLoaderForm() ? (
<div className={styles.loaderWrapper}><LoadingIndicator /></div> <div className={styles.loaderWrapper}>
) : this.renderFirstBlock()} <LoadingIndicator />
</div>
) : (
this.renderFirstBlock()
)}
</div> </div>
<div className="row" style={{ marginRight: '-30px'}}> <div className="row" style={{ marginRight: '-30px' }}>
{this.showLoaderPermissions() && ( {this.showLoaderPermissions() && (
<div className={styles.loaderWrapper} style={{ minHeight: '400px' }}> <div
className={styles.loaderWrapper}
style={{ minHeight: '400px' }}
>
<LoadingIndicator /> <LoadingIndicator />
</div> </div>
)} )}
{!this.showLoaderPermissions() && ( {!this.showLoaderPermissions() && (
<Plugins <Plugins
plugins={get(this.props.editPage, ['modifiedData', 'permissions'])} plugins={get(this.props.editPage, [
'modifiedData',
'permissions',
])}
/> />
)} )}
<Policies <Policies
shouldDisplayPoliciesHint={this.props.editPage.shouldDisplayPoliciesHint} shouldDisplayPoliciesHint={
this.props.editPage.shouldDisplayPoliciesHint
}
inputSelectName={this.props.editPage.inputPoliciesPath} inputSelectName={this.props.editPage.inputPoliciesPath}
routes={this.props.editPage.routes} routes={this.props.editPage.routes}
selectOptions={this.props.editPage.policies} selectOptions={this.props.editPage.policies}
@ -331,8 +368,15 @@ function mapDispatchToProps(dispatch) {
); );
} }
const withConnect = connect(mapStateToProps, mapDispatchToProps); const withConnect = connect(
const withReducer = strapi.injectReducer({ key: 'editPage', reducer, pluginId }); mapStateToProps,
mapDispatchToProps,
);
const withReducer = strapi.injectReducer({
key: 'editPage',
reducer,
pluginId,
});
const withSaga = strapi.injectSaga({ key: 'editPage', saga, pluginId }); const withSaga = strapi.injectSaga({ key: 'editPage', saga, pluginId });
export default compose( export default compose(

View File

@ -1,15 +1,5 @@
import { LOCATION_CHANGE } from 'react-router-redux'; import { all, call, fork, put, select, takeLatest } from 'redux-saga/effects';
import { import { request } from 'strapi-helper-plugin';
all,
call,
cancel,
fork,
put,
select,
take,
takeLatest,
} from 'redux-saga/effects';
import request from 'utils/request';
import { import {
getPermissionsSucceeded, getPermissionsSucceeded,
getPoliciesSucceeded, getPoliciesSucceeded,
@ -33,10 +23,14 @@ import {
export function* fetchUser(action) { export function* fetchUser(action) {
try { try {
const data = yield call(request, `/users-permissions/search/${action.user}`, { method: 'GET' }); const data = yield call(
request,
`/users-permissions/search/${action.user}`,
{ method: 'GET' },
);
yield put(getUserSucceeded(data)); yield put(getUserSucceeded(data));
} catch(error) { } catch (error) {
strapi.notification.error('users-permissions.notification.error.fetchUser'); strapi.notification.error('users-permissions.notification.error.fetchUser');
} }
} }
@ -51,8 +45,10 @@ export function* permissionsGet() {
}); });
yield put(getPermissionsSucceeded(response)); yield put(getPermissionsSucceeded(response));
} catch(err) { } catch (err) {
strapi.notification.error('users-permissions.EditPage.notification.permissions.error'); strapi.notification.error(
'users-permissions.EditPage.notification.permissions.error',
);
} }
} }
@ -62,11 +58,13 @@ export function* policiesGet() {
call(request, '/users-permissions/policies', { method: 'GET' }), call(request, '/users-permissions/policies', { method: 'GET' }),
call(request, '/users-permissions/routes', { method: 'GET' }), call(request, '/users-permissions/routes', { method: 'GET' }),
]); ]);
yield put(getPoliciesSucceeded(policies)); yield put(getPoliciesSucceeded(policies));
yield put(getRoutesSucceeded(routes)); yield put(getRoutesSucceeded(routes));
} catch(err) { } catch (err) {
strapi.notification.error('users-permissions.EditPage.notification.policies.error'); strapi.notification.error(
'users-permissions.EditPage.notification.policies.error',
);
} }
} }
@ -80,8 +78,10 @@ export function* roleGet(action) {
}); });
yield put(getRoleSucceeded(role)); yield put(getRoleSucceeded(role));
} catch(err) { } catch (err) {
strapi.notification.error('users-permissions.EditPage.notification.role.error'); strapi.notification.error(
'users-permissions.EditPage.notification.role.error',
);
} }
} }
@ -95,32 +95,35 @@ export function* submit(action) {
body, body,
}; };
const requestURL = actionType === 'POST' ? '/users-permissions/roles' : `/users-permissions/roles/${roleId}`; const requestURL =
actionType === 'POST'
? '/users-permissions/roles'
: `/users-permissions/roles/${roleId}`;
const response = yield call(request, requestURL, opts); const response = yield call(request, requestURL, opts);
if (actionType === 'POST') { if (actionType === 'POST') {
action.context.emitEvent('didCreateRole'); action.context.emitEvent('didCreateRole');
} }
if (response.ok) { if (response.ok) {
yield put(submitSucceeded()); yield put(submitSucceeded());
} }
} catch(error) { } catch (error) {
console.log(error); // eslint-disable-line no-console console.log(error); // eslint-disable-line no-console
} }
} }
export default function* defaultSaga() { export default function* defaultSaga() {
const loadPermissionsWatcher = yield fork(takeLatest, GET_PERMISSIONS, permissionsGet); yield fork(takeLatest, GET_PERMISSIONS, permissionsGet);
const loadPoliciesWatcher = yield fork(takeLatest, GET_POLICIES, policiesGet); yield fork(takeLatest, GET_POLICIES, policiesGet);
const loadRoleWatcher = yield fork(takeLatest, GET_ROLE, roleGet); yield fork(takeLatest, GET_ROLE, roleGet);
yield fork(takeLatest, GET_USER, fetchUser); yield fork(takeLatest, GET_USER, fetchUser);
yield fork(takeLatest, SUBMIT, submit); yield fork(takeLatest, SUBMIT, submit);
yield take(LOCATION_CHANGE); // yield take(LOCATION_CHANGE);
yield cancel(loadPermissionsWatcher); // yield cancel(loadPermissionsWatcher);
yield cancel(loadPoliciesWatcher); // yield cancel(loadPoliciesWatcher);
yield cancel(loadRoleWatcher); // yield cancel(loadRoleWatcher);
} }

View File

@ -12,8 +12,8 @@ import { bindActionCreators, compose } from 'redux';
import cn from 'classnames'; import cn from 'classnames';
import { clone, get, includes, isEqual, isEmpty } from 'lodash'; import { clone, get, includes, isEqual, isEmpty } from 'lodash';
import HeaderNav from 'components/HeaderNav'; import { HeaderNav, PluginHeader } from 'strapi-helper-plugin';
import PluginHeader from 'components/PluginHeader'; // import PluginHeader from 'components/PluginHeader';
import pluginId from '../../pluginId'; import pluginId from '../../pluginId';
@ -257,7 +257,7 @@ export class HomePage extends React.Component {
{component} {component}
</div> </div>
<PopUpForm <PopUpForm
actionType='edit' actionType="edit"
isOpen={this.state.showModalEdit} isOpen={this.state.showModalEdit}
dataToEdit={dataToEdit} dataToEdit={dataToEdit}
didCheckErrors={didCheckErrors} didCheckErrors={didCheckErrors}

View File

@ -10,7 +10,7 @@ import {
call, call,
} from 'redux-saga/effects'; } from 'redux-saga/effects';
import request from 'utils/request'; import { request } from 'strapi-helper-plugin';
import { import {
deleteDataSucceeded, deleteDataSucceeded,
@ -19,11 +19,7 @@ import {
submitSucceeded, submitSucceeded,
} from './actions'; } from './actions';
import { import { DELETE_DATA, FETCH_DATA, SUBMIT } from './constants';
DELETE_DATA,
FETCH_DATA,
SUBMIT,
} from './constants';
import { import {
makeSelectAllData, makeSelectAllData,
@ -37,7 +33,10 @@ export function* dataDelete() {
const allData = yield select(makeSelectAllData()); const allData = yield select(makeSelectAllData());
const dataToDelete = yield select(makeSelectDataToDelete()); const dataToDelete = yield select(makeSelectDataToDelete());
const endPointAPI = yield select(makeSelectDeleteEndPoint()); const endPointAPI = yield select(makeSelectDeleteEndPoint());
const indexDataToDelete = findIndex(allData[endPointAPI], ['name', dataToDelete.name]); const indexDataToDelete = findIndex(allData[endPointAPI], [
'name',
dataToDelete.name,
]);
if (indexDataToDelete !== -1) { if (indexDataToDelete !== -1) {
const id = dataToDelete.id; const id = dataToDelete.id;
@ -46,17 +45,23 @@ export function* dataDelete() {
if (response.ok) { if (response.ok) {
yield put(deleteDataSucceeded(indexDataToDelete)); yield put(deleteDataSucceeded(indexDataToDelete));
strapi.notification.success('users-permissions.notification.success.delete'); strapi.notification.success(
'users-permissions.notification.success.delete',
);
} }
} }
} catch(err) { } catch (err) {
strapi.notification.error('users-permissions.notification.error.delete'); strapi.notification.error('users-permissions.notification.error.delete');
} }
} }
export function* dataFetch(action) { export function* dataFetch(action) {
try { try {
const response = yield call(request, `/users-permissions/${action.endPoint}`, { method: 'GET' }); const response = yield call(
request,
`/users-permissions/${action.endPoint}`,
{ method: 'GET' },
);
if (action.endPoint === 'advanced') { if (action.endPoint === 'advanced') {
yield put(setForm(response)); yield put(setForm(response));
@ -64,7 +69,7 @@ export function* dataFetch(action) {
const data = response[action.endPoint] || response; const data = response[action.endPoint] || response;
yield put(fetchDataSucceeded(data)); yield put(fetchDataSucceeded(data));
} }
} catch(err) { } catch (err) {
strapi.notification.error('users-permissions.notification.error.fetch'); strapi.notification.error('users-permissions.notification.error.fetch');
} }
} }
@ -72,7 +77,13 @@ export function* dataFetch(action) {
export function* submitData(action) { export function* submitData(action) {
try { try {
const body = yield select(makeSelectModifiedData()); const body = yield select(makeSelectModifiedData());
const opts = { method: 'PUT', body: (action.endPoint === 'advanced') ? get(body, ['advanced', 'settings'], {}) : body }; const opts = {
method: 'PUT',
body:
action.endPoint === 'advanced'
? get(body, ['advanced', 'settings'], {})
: body,
};
yield call(request, `/users-permissions/${action.endPoint}`, opts); yield call(request, `/users-permissions/${action.endPoint}`, opts);
@ -83,8 +94,10 @@ export function* submitData(action) {
} }
yield put(submitSucceeded()); yield put(submitSucceeded());
strapi.notification.success('users-permissions.notification.success.submit'); strapi.notification.success(
} catch(error) { 'users-permissions.notification.success.submit',
);
} catch (error) {
strapi.notification.error('notification.error'); strapi.notification.error('notification.error');
} }
} }

View File

@ -1,6 +1,6 @@
import { fork, takeLatest, call, put } from 'redux-saga/effects'; import { fork, takeLatest, call, put } from 'redux-saga/effects';
import request from 'utils/request'; import { request } from 'strapi-helper-plugin';
import { INITIALIZE } from './constants'; import { INITIALIZE } from './constants';
import { initializeSucceeded } from './actions'; import { initializeSucceeded } from './actions';

View File

@ -11,7 +11,7 @@
import React from 'react'; import React from 'react';
import NotFound from 'components/NotFound'; import { NotFound } from 'strapi-helper-plugin';
export default class NotFoundPage extends React.Component { export default class NotFoundPage extends React.Component {
render() { render() {

View File

@ -0,0 +1,93 @@
import React from 'react';
import { reduce } from 'lodash';
import pluginPkg from '../../package.json';
import pluginId from './pluginId';
import App from './containers/App';
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
const formatMessages = messages =>
reduce(
messages,
(result, value, key) => {
result[`${pluginId}.${key}`] = value;
return result;
},
{},
);
const requireTranslations = language => {
try {
return require(`./translations/${language}.json`); // eslint-disable-line global-require
} catch (error) {
console.error(
`Unable to load "${language}" translation for the plugin ${pluginId}. Please make sure "${language}.json" file exists in "pluginPath/admin/src/translations" folder.`,
);
return;
}
};
const translationMessages = reduce(
strapi.languages,
(result, language) => {
result[language] = formatMessages(requireTranslations(language));
return result;
},
{},
);
const layout = (() => {
try {
return require('../../config/layout.js'); // eslint-disable-line import/no-unresolved
} catch (err) {
return null;
}
})();
const injectedComponents = (() => {
try {
return require('./injectedComponents').default; // eslint-disable-line import/no-unresolved
} catch (err) {
return [];
}
})();
const initializer = (() => {
try {
return require('./initializer');
} catch (err) {
return null;
}
})();
const lifecycles = (() => {
try {
return require('./lifecycles');
} catch (err) {
return null;
}
})();
function Comp(props) {
return <App {...props} />;
}
const plugin = {
blockerComponent: null,
blockerComponentProps: {},
description: pluginDescription,
icon: pluginPkg.strapi.icon,
id: pluginId,
initializer,
injectedComponents,
layout,
lifecycles,
leftMenuLinks: [],
leftMenuSections: [],
mainComponent: Comp,
name: pluginPkg.strapi.name,
preventComponentRendering: false,
translationMessages,
};
export default plugin;

View File

@ -1,4 +1,4 @@
const auth = require('utils/auth').default; const { auth } = require('strapi-helper-plugin');
module.exports = function willSecure() { module.exports = function willSecure() {
const { const {

View File

@ -28,6 +28,7 @@
"koa": "^2.1.0", "koa": "^2.1.0",
"koa2-ratelimit": "^0.6.1", "koa2-ratelimit": "^0.6.1",
"purest": "^2.0.1", "purest": "^2.0.1",
"react": "^16.8.6",
"request": "^2.83.0", "request": "^2.83.0",
"strapi-utils": "3.0.0-alpha.25.2", "strapi-utils": "3.0.0-alpha.25.2",
"uuid": "^3.1.0" "uuid": "^3.1.0"
@ -56,4 +57,4 @@
"npm": ">= 6.0.0" "npm": ">= 6.0.0"
}, },
"license": "MIT" "license": "MIT"
} }