Created InstallPluginPage container

This commit is contained in:
cyril lopez 2017-12-02 13:37:58 +01:00
parent 86a5401680
commit 046574fb45
15 changed files with 349 additions and 272 deletions

View File

@ -24,7 +24,7 @@ class LeftMenuLink extends React.Component { // eslint-disable-line react/prefer
className={`${styles.link} ${isLinkActive ? styles.linkActive : ''}`}
to={{
pathname: this.props.destination,
search: `?source=${this.props.source}`,
search: `${this.props.source ? '?source=' : ''}${this.props.source}`,
}}
>
<i className={`${styles.linkIcon} fa-${this.props.icon} fa`}></i>

View File

@ -25,6 +25,7 @@ import ComingSoonPage from 'containers/ComingSoonPage';
import Content from 'containers/Content';
import Header from 'components/Header/index';
import HomePage from 'containers/HomePage';
import InstallPluginPage from 'containers/InstallPluginPage';
import LeftMenu from 'containers/LeftMenu';
import ListPluginsPage from 'containers/ListPluginsPage';
import Logout from 'components/Logout';
@ -111,7 +112,7 @@ export class AdminPage extends React.Component { // eslint-disable-line react/pr
<Route path="/plugins/:pluginId" component={PluginPage} />
<Route path="/plugins" component={ComingSoonPage} />
<Route path="/list-plugins" component={ListPluginsPage} exact />
<Route path="/install-plugin" component={ComingSoonPage} exact />
<Route path="/install-plugin" component={InstallPluginPage} exact />
<Route path="/configuration" component={ComingSoonPage} exact />
<Route path="" component={NotFoundPage} />
</Switch>

View File

@ -0,0 +1,32 @@
/*
*
* InstallPluginPage actions
*
*/
import {
GET_PLUGINS,
GET_PLUGINS_SUCCEEDED,
ON_CHANGE,
} from './constants';
export function getPlugins() {
return {
type: GET_PLUGINS,
};
}
export function getPluginsSucceeded(availablePlugins) {
return {
type: GET_PLUGINS_SUCCEEDED,
availablePlugins,
};
}
export function onChange({ target }) {
return {
type: ON_CHANGE,
keys: target.name.split('.'),
value: target.value,
};
}

View File

@ -0,0 +1,9 @@
/*
*
* InstallPluginPage constants
*
*/
export const GET_PLUGINS = 'StrapiAdmin/InstallPluginPage/GET_PLUGINS';
export const GET_PLUGINS_SUCCEEDED = 'StrapiAdmin/InstallPluginPage/GET_PLUGINS_SUCCEEDED';
export const ON_CHANGE = 'StrapiAdmin/InstallPluginPage/ON_CHANGE';

View File

@ -0,0 +1,32 @@
{
"availablePlugins": [
{
"description": "content-manager.plugin.description",
"id": "content-manager",
"icon": "plug",
"name": "Content Manager",
"price": 0
},
{
"description": "content-type-builder.plugin.description",
"id": "content-type-builder",
"icon": "brush",
"name": "content type builder",
"price": 0
},
{
"description": "settings-manager.plugin.description",
"id": "settings-manager",
"icon": "wrench",
"name": "settings manager",
"price": 0
},
{
"description": "users-permissions.plugin.description",
"id": "users-permissions",
"icon": "users",
"name": "Auth & Permissions",
"price": 0
}
]
}

View File

@ -0,0 +1,115 @@
/**
*
* InstallPluginPage
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Helmet } from 'react-helmet';
import { FormattedMessage } from 'react-intl';
import { bindActionCreators, compose } from 'redux';
import cn from 'classnames';
// Design
import Input from 'components/Input';
import PluginHeader from 'components/PluginHeader';
import injectSaga from 'utils/injectSaga';
import injectReducer from 'utils/injectReducer';
import {
getPlugins,
onChange,
} from './actions';
import makeSelectInstallPluginPage from './selectors';
import reducer from './reducer';
import saga from './saga';
import styles from './styles.scss';
export class InstallPluginPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
componentDidMount() {
// Don't fetch the available plugins if it has already been done
if (!this.props.didFetchPlugins) {
this.props.getPlugins();
}
}
render() {
return (
<div>
<FormattedMessage id="app.components.InstallPluginPage.helmet">
{message => (
<Helmet>
<title>{message}</title>
<meta name="description" content="Description of InstallPluginPage" />
</Helmet>
)}
</FormattedMessage>
<div className={cn('container-fluid', styles.containerFluid)}>
<PluginHeader
title={{ id: 'app.components.InstallPluginPage.title' }}
description={{ id: 'app.components.InstallPluginPage.description' }}
actions={[]}
/>
<div className="row">
<Input
customBootstrapClass="col-md-12"
label="app.components.InstallPluginPage.InputSearch.label"
name="search"
onChange={this.props.onChange}
placeholder="app.components.InstallPluginPage.InputSearch.placeholder"
type="search"
validations={{}}
value={this.props.search}
/>
</div>
</div>
</div>
);
}
}
InstallPluginPage.contextTypes = {
plugins: PropTypes.object.isRequired,
};
InstallPluginPage.propTypes = {
didFetchPlugins: PropTypes.bool.isRequired,
getPlugins: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
search: PropTypes.string.isRequired,
};
const mapStateToProps = makeSelectInstallPluginPage();
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
getPlugins,
onChange,
},
dispatch,
);
}
const withConnect = connect(mapStateToProps, mapDispatchToProps);
/* Remove this line if the container doesn't have a route and
* check the documentation to see how to create the container's store
*/
const withReducer = injectReducer({ key: 'installPluginPage', reducer });
/* Remove the line below the container doesn't have a route and
* check the documentation to see how to create the container's store
*/
const withSaga = injectSaga({ key: 'installPluginPage', saga });
export default compose(
withReducer,
withSaga,
withConnect,
)(InstallPluginPage);

View File

@ -0,0 +1,32 @@
/*
*
* InstallPluginPage reducer
*
*/
import { fromJS, List } from 'immutable';
import {
GET_PLUGINS_SUCCEEDED,
ON_CHANGE,
} from './constants';
const initialState = fromJS({
availablePlugins: List([]),
didFetchPlugins: false,
search: '',
});
function installPluginPageReducer(state = initialState, action) {
switch (action.type) {
case GET_PLUGINS_SUCCEEDED:
return state
.set('didFetchPlugins', true)
.set('plugins', List(action.availablePlugins));
case ON_CHANGE:
return state.updateIn(action.keys, () => action.value);
default:
return state;
}
}
export default installPluginPageReducer;

View File

@ -0,0 +1,44 @@
import { LOCATION_CHANGE } from 'react-router-redux';
import {
// call,
cancel,
fork,
put,
// select,
take,
takeLatest,
} from 'redux-saga/effects';
// import request from 'utils/request';
import fakeData from './fakeData.json';
import { getPluginsSucceeded } from './actions';
import { GET_PLUGINS } from './constants';
export function* pluginsGet() {
try {
const availablePlugins = fakeData.availablePlugins;
const supportUs = {
description: 'app.components.InstallPluginPage.plugin.support-us.description',
id: 'support-us',
icon: '',
name: 'buy a t-shirt',
price: 30,
};
yield put(getPluginsSucceeded(availablePlugins.concat([supportUs])));
} catch(err) {
strapi.notification.error('notification.error');
}
}
// Individual exports for testing
export default function* defaultSaga() {
const loadPluginsWatcher = yield fork(takeLatest, GET_PLUGINS, pluginsGet);
yield take(LOCATION_CHANGE);
yield cancel(loadPluginsWatcher);
}

View File

@ -0,0 +1,25 @@
import { createSelector } from 'reselect';
/**
* Direct selector to the installPluginPage state domain
*/
const selectInstallPluginPageDomain = () => (state) => state.get('installPluginPage');
/**
* Other specific selectors
*/
/**
* Default selector used by InstallPluginPage
*/
const makeSelectInstallPluginPage = () => createSelector(
selectInstallPluginPageDomain(),
(substate) => substate.toJS()
);
export default makeSelectInstallPluginPage;
export {
selectInstallPluginPageDomain,
};

View File

@ -0,0 +1,6 @@
.containerFluid {
padding: 18px 30px !important;
> div:first-child {
max-height: 33px;
}
}

View File

@ -1,11 +1,20 @@
{
"app.components.ComingSoonPage.comingSoon": "Coming soon",
"app.components.ComingSoonPage.featuresNotAvailable": "This feature is still under active development.",
"app.components.HomePage.welcome": "Welcome on board!",
"app.components.HomePage.description.part1": "We are happy to have you as one of our users!",
"app.components.HomePage.description.part2": "You are now a member of our community which will help you building your dreamed app.",
"app.components.HomePage.button": "Create your first content type",
"app.components.HomePage.feedback": "Feel free to ask questions or give us feedback by using one of the support channels below.",
"app.components.InstallPluginPage.helmet": "Install plugins",
"app.components.InstallPluginPage.title": "Install new plugins",
"app.components.InstallPluginPage.description": "Extend your app with no efforts",
"app.components.InstallPluginPage.plugin.support-us.description": "Support us by buying the Strapi t-shirt. It will allow us to keep working on the project and try giving the best possible experience!",
"app.components.InstallPluginPage.InputSearch.label": " ",
"app.components.InstallPluginPage.InputSearch.placeholder": "Search a plugin... (ex: authentication)",
"app.components.LeftMenuFooter.poweredBy": "Proudly powered by",
"app.components.LeftMenuLinkContainer.configuration": "Configuration",
"app.components.LeftMenuLinkContainer.general": "General",
@ -13,8 +22,10 @@
"app.components.LeftMenuLinkContainer.listPlugins": "List plugins",
"app.components.LeftMenuLinkContainer.noPluginsInstalled": "No plugins installed yet",
"app.components.LeftMenuLinkContainer.plugins": "Plugins",
"app.components.NotFoundPage.description": "Not Found",
"app.components.NotFoundPage.back": "Back to homepage",
"app.components.ListPluginsPage.helmet.title": "List plugins",
"app.components.ListPluginsPage.title": "Plugins",
"app.components.ListPluginsPage.description": "List of the installed plugins in the project.",
@ -23,14 +34,20 @@
"app.components.listPlugins.title.plural": "{number} plugins are installed",
"app.components.listPlugins.title.none": "No plugin is installed",
"app.components.listPlugins.button": "Add New Plugin",
"components.AutoReloadBlocker.header": "Reload feature is required for this plugin.",
"components.AutoReloadBlocker.description": "Open the following file and enable the feature.",
"components.ErrorBoundary.title": "Something wen't wrong...",
"components.ProductionBlocker.header": "This plugin is only available in development.",
"components.ProductionBlocker.description": "For safety we have to disable this plugin in other environments.",
"components.popUpWarning.button.cancel": "Cancel",
"components.popUpWarning.button.confirm": "Confirm",
"components.popUpWarning.title": "Please confirm",
"components.popUpWarning.message": "Are you sure you want to delete this?",
"components.Input.error.validation.email": "This is not an email",
"components.Input.error.validation.required": "This value input is required.",
"components.Input.error.validation.regex": "The value not match the regex.",
@ -43,8 +60,11 @@
"components.Input.error.attribute.key.taken": "This value already exists",
"components.Input.error.attribute.sameKeyAndName": "Can't be equals",
"components.Input.error.validation.minSupMax": "Can't be superior",
"components.ErrorBoundary.title": "Something wen't wrong...",
"components.ListRow.empty": "There is no data to be shown.",
"notification.error": "An error occurred",
"Auth & Permissions": "Auth & Permissions",
"Content Manager": "Content Manager",
"Content Type Builder": "Content Type Builder",

View File

@ -1,11 +1,21 @@
{
"app.components.ComingSoonPage.comingSoon": "Bientôt disponible",
"app.components.ComingSoonPage.featuresNotAvailable": "Cette fonctionnalité est toujours en cours de développement.",
"app.components.HomePage.welcome": "Bienvenue à bord!",
"app.components.HomePage.description.part1": "Nous somme heureux de vous compter parmi nos utilisateurs",
"app.components.HomePage.description.part2": "Vous faites désormais parti de notre communauté qui peut vous aider à développer votre application.",
"app.components.HomePage.button": "Créez votre premier type de contenu",
"app.components.HomePage.feedback": "N'hésitez pas à utiliser un des channels ci-dessous pour poser vos questions ou nous donner vos retours.",
"app.components.InstallPluginPage.helmet": "Installez des plugins",
"app.components.InstallPluginPage.title": "Installez des nouveaux plugins",
"app.components.InstallPluginPage.description": "Améliorez votre app sans efforts",
"app.components.InstallPluginPage.plugin.support-us.description": "Soutenez-nous en achetant un Strapi tee shirt. Cela nous aidera a continuer de travailler sur le projet pour vous donner la meilleure expérience possible!",
"app.components.InstallPluginPage.InputSearch.label": " ",
"app.components.InstallPluginPage.InputSearch.placeholder": "Recherchez un plugin... (ex: authentification)",
"app.components.LeftMenuFooter.poweredBy": "Propulsé par",
"app.components.LeftMenuLinkContainer.configuration": "Configuration",
"app.components.LeftMenuLinkContainer.general": "Général",
@ -13,8 +23,10 @@
"app.components.LeftMenuLinkContainer.listPlugins": "Liste des plugins",
"app.components.LeftMenuLinkContainer.noPluginsInstalled": "Aucun plugin installé",
"app.components.LeftMenuLinkContainer.plugins": "Plugins",
"app.components.NotFoundPage.description": "Page introuvable",
"app.components.NotFoundPage.back": "Retourner à la page d'accueil",
"app.components.ListPluginsPage.helmet.title": "List plugins",
"app.components.ListPluginsPage.title": "Plugins",
"app.components.ListPluginsPage.description": "Liste des plugins installés dans le projet.",
@ -23,14 +35,20 @@
"app.components.listPlugins.title.plural": "{number} sont disponibles",
"app.components.listPlugins.title.none": "Aucun plugin n'est installé",
"app.components.listPlugins.button": "Ajouter un Nouveau Plugin",
"components.AutoReloadBlocker.header": "L'autoReload doit être activé pour ce plugin.",
"components.AutoReloadBlocker.description": "Ouvrez le fichier suivant pour activer cette fonctionnalité.",
"components.ErrorBoundary.title": "Une erreur est survenue...",
"components.ProductionBlocker.header": "Ce plugin est disponible uniquement en développement.",
"components.ProductionBlocker.description": "Pour des raisons de sécurité il est désactivé dans les autres environnements.",
"comonents.popUpWarning.button.cancel": "Annuler",
"comonents.popUpWarning.button.confirm": "Confirmer",
"components.popUpWarning.button.cancel": "Annuler",
"components.popUpWarning.button.confirm": "Confirmer",
"components.popUpWarning.title": "Merci de confirmer",
"components.popUpWarning.message": "Etes-vous sure de vouloir le supprimer?",
"components.Input.error.validation.email": "Le format n'est pas de type email",
"components.Input.error.validation.required": "Ce champ est obligatoire.",
"components.Input.error.validation.regex": "La valeur ne correspond pas au format attendu.",
@ -43,8 +61,11 @@
"components.Input.error.attribute.key.taken": "Cette valeur existe déjà",
"components.Input.error.attribute.sameKeyAndName": "Ne peuvent pas être égaux",
"components.Input.error.validation.minSupMax": "Ne peut pas être plus grand",
"components.ErrorBoundary.title": "Une erreur est survenue...",
"components.ListRow.empty": "Il n'y a pas de données à afficher.",
"notification.error": "Une erreur est survenue",
"Auth & Permissions": "Auth & Permissions",
"Content Manager": "Content Manager",
"Content Type Builder": "Content Type Builder",

View File

@ -483,6 +483,8 @@ class Input extends React.Component { // eslint-disable-line react/prefer-statel
return this.renderInputToggle();
case 'email':
return this.renderInputEmail(requiredClass, inputDescription, handleBlur);
case 'search':
return this.renderInputSearch(requiredClass, inputDescription, handleBlur)
default:
}

View File

@ -256,138 +256,7 @@
}
},
"application": {
"controllers": {
"azeaz": {
"find": {
"enabled": true,
"policy": ""
},
"findOne": {
"enabled": true,
"policy": ""
},
"create": {
"enabled": true,
"policy": ""
},
"update": {
"enabled": true,
"policy": ""
},
"destroy": {
"enabled": true,
"policy": ""
},
"identity": {
"enabled": true,
"policy": ""
}
},
"bite": {
"find": {
"enabled": true,
"policy": ""
},
"findOne": {
"enabled": true,
"policy": ""
},
"create": {
"enabled": true,
"policy": ""
},
"update": {
"enabled": true,
"policy": ""
},
"destroy": {
"enabled": true,
"policy": ""
},
"identity": {
"enabled": true,
"policy": ""
}
},
"erza": {
"find": {
"enabled": true,
"policy": ""
},
"findOne": {
"enabled": true,
"policy": ""
},
"create": {
"enabled": true,
"policy": ""
},
"update": {
"enabled": true,
"policy": ""
},
"destroy": {
"enabled": true,
"policy": ""
},
"identity": {
"enabled": true,
"policy": ""
}
},
"ez": {
"find": {
"enabled": true,
"policy": ""
},
"findOne": {
"enabled": true,
"policy": ""
},
"create": {
"enabled": true,
"policy": ""
},
"update": {
"enabled": true,
"policy": ""
},
"destroy": {
"enabled": true,
"policy": ""
},
"identity": {
"enabled": true,
"policy": ""
}
},
"reaz": {
"find": {
"enabled": true,
"policy": ""
},
"findOne": {
"enabled": true,
"policy": ""
},
"create": {
"enabled": true,
"policy": ""
},
"update": {
"enabled": true,
"policy": ""
},
"destroy": {
"enabled": true,
"policy": ""
},
"identity": {
"enabled": true,
"policy": ""
}
}
}
"controllers": {}
}
}
},
@ -648,138 +517,7 @@
}
},
"application": {
"controllers": {
"azeaz": {
"find": {
"enabled": false,
"policy": ""
},
"findOne": {
"enabled": false,
"policy": ""
},
"create": {
"enabled": false,
"policy": ""
},
"update": {
"enabled": false,
"policy": ""
},
"destroy": {
"enabled": false,
"policy": ""
},
"identity": {
"enabled": false,
"policy": ""
}
},
"bite": {
"find": {
"enabled": false,
"policy": ""
},
"findOne": {
"enabled": false,
"policy": ""
},
"create": {
"enabled": false,
"policy": ""
},
"update": {
"enabled": false,
"policy": ""
},
"destroy": {
"enabled": false,
"policy": ""
},
"identity": {
"enabled": false,
"policy": ""
}
},
"erza": {
"find": {
"enabled": false,
"policy": ""
},
"findOne": {
"enabled": false,
"policy": ""
},
"create": {
"enabled": false,
"policy": ""
},
"update": {
"enabled": false,
"policy": ""
},
"destroy": {
"enabled": false,
"policy": ""
},
"identity": {
"enabled": false,
"policy": ""
}
},
"ez": {
"find": {
"enabled": false,
"policy": ""
},
"findOne": {
"enabled": false,
"policy": ""
},
"create": {
"enabled": false,
"policy": ""
},
"update": {
"enabled": false,
"policy": ""
},
"destroy": {
"enabled": false,
"policy": ""
},
"identity": {
"enabled": false,
"policy": ""
}
},
"reaz": {
"find": {
"enabled": false,
"policy": ""
},
"findOne": {
"enabled": false,
"policy": ""
},
"create": {
"enabled": false,
"policy": ""
},
"update": {
"enabled": false,
"policy": ""
},
"destroy": {
"enabled": false,
"policy": ""
},
"identity": {
"enabled": false,
"policy": ""
}
}
}
"controllers": {}
}
}
}

View File

@ -47,7 +47,7 @@
"license": "MIT",
"devDependencies": {
"cross-env": "^5.1.1",
"eslint": "^4.12.0",
"eslint": "^4.12.1",
"eslint-config-airbnb": "^15.1.0",
"eslint-config-airbnb-base": "^11.3.2",
"eslint-config-prettier": "^2.9.0",
@ -59,6 +59,6 @@
"plop": "^1.9.0",
"prettier": "^1.8.2",
"rimraf": "^2.6.2",
"webpack": "^3.8.1"
"webpack": "^3.9.1"
}
}