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
import history from './utils/history';
import plugins from './plugins';
const initialState = {};
const store = configureStore(initialState, history);
const { dispatch } = store;
@ -47,7 +50,18 @@ const MOUNT_NODE = document.getElementById('app');
// 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
const remoteURL = (() => {
@ -63,14 +77,6 @@ const remoteURL = (() => {
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) => {
dispatch(showNotification(message, status));
};
@ -85,7 +91,6 @@ window.strapi = Object.assign(window.strapi || {}, {
node: MODE || 'host',
remoteURL,
backendURL: BACKEND_URL,
registerPlugin,
notification: {
success: message => {
displayNotification(message, 'success');

View File

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

View File

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

View File

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

View File

@ -14,12 +14,19 @@ import { IntlProvider } from 'react-intl';
import { defaultsDeep } from 'lodash';
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() {
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 (
<IntlProvider locale={this.props.locale} defaultLocale="en" messages={messages}>
<IntlProvider
locale={this.props.locale}
defaultLocale="en"
messages={messages}
>
{React.Children.only(this.props.children)}
</IntlProvider>
);
@ -32,10 +39,9 @@ LanguageProvider.propTypes = {
messages: PropTypes.object.isRequired,
};
const mapStateToProps = createSelector(
selectLocale(),
(locale) => ({ locale })
locale => ({ locale }),
);
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": {
"@babel/polyfill": "^7.4.3",
"@babel/runtime": "^7.4.3",
"classnames": "^2.2.6",
"crypto": "^1.0.1",
"friendly-errors-webpack-plugin": "^1.7.0",
"history": "^4.9.0",
"hoist-non-react-statics": "^3.3.0",
"html-webpack-plugin": "^3.2.0",
"immutable": "^3.8.2",
"intl": "^1.2.5",
"invariant": "^2.2.4",
@ -56,7 +56,6 @@
"redux-saga": "^0.16.0",
"remove-markdown": "^0.2.2",
"reselect": "^3.0.1",
"shelljs": "^0.7.8",
"strapi-helper-plugin": "3.0.0-alpha.25.2",
"video-react": "^0.13.2"
},
@ -74,7 +73,9 @@
"css-loader": "^2.1.1",
"duplicate-package-checker-webpack-plugin": "^3.0.0",
"file-loader": "^3.0.1",
"friendly-errors-webpack-plugin": "^1.7.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"image-webpack-loader": "^4.6.0",
"mini-css-extract-plugin": "^0.6.0",
"node-sass": "^4.11.0",
@ -84,6 +85,7 @@
"precss": "^4.0.0",
"sanitize.css": "^4.1.0",
"sass-loader": "^7.1.0",
"shelljs": "^0.7.8",
"simple-progress-webpack-plugin": "^1.1.2",
"strapi-utils": "3.0.0-alpha.25.2",
"style-loader": "^0.23.1",
@ -110,4 +112,4 @@
"npm": ">= 6.0.0"
},
"license": "MIT"
}
}

View File

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

View File

@ -91,11 +91,6 @@ module.exports = {
{
test: /\.m?js$/,
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: {
loader: require.resolve('babel-loader'),
options: {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,18 +1,9 @@
// import { LOCATION_CHANGE } from 'react-router-redux';
import { all, call, fork, put, select, takeLatest } from 'redux-saga/effects';
import request from 'utils/request';
import {
getSettingsSucceeded,
submitSucceeded,
} from './actions';
import {
GET_SETTINGS,
SUBMIT,
} from './constants';
import {
makeSelectEnv,
makeSelectModifiedData,
} from './selectors';
import { request } from 'strapi-helper-plugin';
import { getSettingsSucceeded, submitSucceeded } from './actions';
import { GET_SETTINGS, SUBMIT } from './constants';
import { makeSelectEnv, makeSelectModifiedData } from './selectors';
export function* settingsGet(action) {
try {
@ -23,7 +14,7 @@ export function* settingsGet(action) {
]);
yield put(getSettingsSucceeded(response[0], response[1].environments));
} catch(err) {
} catch (err) {
strapi.notification.error('notification.error');
}
}
@ -46,7 +37,7 @@ export function* submit() {
// Update reducer with optimisticResponse
strapi.notification.success('email.notification.config.success');
yield put(submitSucceeded(body));
} catch(err) {
} catch (err) {
strapi.notification.error('notification.error');
// 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 PropTypes from 'prop-types';
import { get } from 'lodash';
import cn from 'classnames';
import LoadingIndicator from 'components/LoadingIndicator';
import Input from 'components/InputsIndex';
import { InputsIndex as Input, LoadingIndicator } from 'strapi-helper-plugin';
import styles from './styles.scss';
class EditForm extends React.Component { // eslint-disable-line react/prefer-stateless-function
generateSelectOptions = () => (
class EditForm extends React.Component {
// eslint-disable-line react/prefer-stateless-function
generateSelectOptions = () =>
Object.keys(get(this.props.values, 'roles', [])).reduce((acc, current) => {
const option = {
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);
return acc;
}, [])
)
}, []);
render() {
if (this.props.showLoaders) {
return (
<div className={cn(styles.editForm, this.props.showLoaders && styles.loadIndicatorContainer)}>
<div
className={cn(
styles.editForm,
this.props.showLoaders && styles.loadIndicatorContainer,
)}
>
<LoadingIndicator />
</div>
);
@ -39,7 +43,9 @@ class EditForm extends React.Component { // eslint-disable-line react/prefer-sta
<div className={styles.editForm}>
<div className="row">
<Input
inputDescription={{ id: 'users-permissions.EditForm.inputSelect.description.role' }}
inputDescription={{
id: 'users-permissions.EditForm.inputSelect.description.role',
}}
inputClassName={styles.inputStyle}
label={{ id: 'users-permissions.EditForm.inputSelect.label.role' }}
name="advanced.settings.default_role"
@ -53,7 +59,9 @@ class EditForm extends React.Component { // eslint-disable-line react/prefer-sta
<div className="row">
<Input
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"
onChange={this.props.onChange}
type="toggle"
@ -89,8 +97,12 @@ class EditForm extends React.Component { // eslint-disable-line react/prefer-sta
*/}
<div className="row">
<Input
label={{ id: 'users-permissions.EditForm.inputToggle.label.sign-up' }}
inputDescription={{ id: 'users-permissions.EditForm.inputToggle.description.sign-up' }}
label={{
id: 'users-permissions.EditForm.inputToggle.label.sign-up',
}}
inputDescription={{
id: 'users-permissions.EditForm.inputToggle.description.sign-up',
}}
name="advanced.settings.allow_register"
onChange={this.props.onChange}
type="toggle"
@ -100,8 +112,14 @@ class EditForm extends React.Component { // eslint-disable-line react/prefer-sta
<div className={styles.separator} />
<div className="row">
<Input
label={{ id: 'users-permissions.EditForm.inputToggle.label.email-confirmation' }}
inputDescription={{ id: 'users-permissions.EditForm.inputToggle.description.email-confirmation' }}
label={{
id:
'users-permissions.EditForm.inputToggle.label.email-confirmation',
}}
inputDescription={{
id:
'users-permissions.EditForm.inputToggle.description.email-confirmation',
}}
name="advanced.settings.email_confirmation"
onChange={this.props.onChange}
type="toggle"
@ -110,12 +128,21 @@ class EditForm extends React.Component { // eslint-disable-line react/prefer-sta
</div>
<div className="row">
<Input
label={{ id: 'users-permissions.EditForm.inputToggle.label.email-confirmation-redirection' }}
inputDescription={{ id: 'users-permissions.EditForm.inputToggle.description.email-confirmation-redirection' }}
label={{
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"
onChange={this.props.onChange}
type="text"
value={get(this.props.values.settings, 'email_confirmation_redirection')}
value={get(
this.props.values.settings,
'email_confirmation_redirection',
)}
/>
</div>
</div>

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
/**
*
* Policies
*
*/
*
* Policies
*
*/
import React from 'react';
import cn from 'classnames';
@ -10,24 +10,42 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
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 styles from './styles.scss';
class Policies extends React.Component { // eslint-disable-line react/prefer-stateless-function
handleChange = (e) => this.context.onChange(e);
class Policies extends React.Component {
// eslint-disable-line react/prefer-stateless-function
handleChange = e => this.context.onChange(e);
render() {
const baseTitle = 'users-permissions.Policies.header';
const title = this.props.shouldDisplayPoliciesHint ? 'hint' : 'title';
const value = get(this.props.values, this.props.inputSelectName);
const path = without(this.props.inputSelectName.split('.'), '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('.')));
const path = without(
this.props.inputSelectName.split('.'),
'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 (
<div className={cn('col-md-5',styles.policies)}>
<div className={cn('col-md-5', styles.policies)}>
<div className="container-fluid">
<div className={cn('row', styles.inputWrapper)}>
<div className={cn('col-md-12', styles.header)}>
@ -44,12 +62,16 @@ class Policies extends React.Component { // eslint-disable-line react/prefer-sta
validations={{}}
value={value}
/>
) : ''}
) : (
''
)}
</div>
<div className="row">
{!this.props.shouldDisplayPoliciesHint ? (
map(routes, (route, key) => <BoundRoute key={key} route={route} />)
) : ''}
{!this.props.shouldDisplayPoliciesHint
? map(routes, (route, key) => (
<BoundRoute key={key} route={route} />
))
: ''}
</div>
</div>
</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
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 { initializeSucceeded } from './actions';

View File

@ -11,7 +11,7 @@
import React from 'react';
import NotFound from 'components/NotFound';
import { NotFound } from 'strapi-helper-plugin';
export default class NotFoundPage extends React.Component {
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() {
const {

View File

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